編輯:關於Android編程
前面在介紹Android系統的開機畫面時提到,Android設備的顯示屏被抽象為一個幀緩沖區,而Android系統中的SurfaceFlinger服務就是通過向這個幀緩沖區寫入內容來繪制應用程序的用戶界面的。Android系統在硬件抽象層中提供了一個Gralloc模塊,封裝了對幀緩沖區的所有訪問操作。本文將詳細分析Gralloc模塊的實現,為後續分析SurfaceFlinger服務的實現打下基礎。
在前面Android系統的開機畫面顯示過程分析一文中提到,Linux內核在啟動的過程中會創建一個類別和名稱分別為“graphics”和“fb0”的設備,用來描述系統中的第一個幀緩沖區,即第一個顯示屏,其中,數字0表示從設備號。注意,系統中至少要存在一個顯示屏,因此,名稱為“fb0”的設備是肯定會存在的,否則的話,就是出錯了。Android系統和Linux內核本身的設計都是支持多個顯示屏的,不過,在Android目前的實現中,只支持一個顯示屏。
在前面Android系統的開機畫面顯示過程分析一文中還提到,init進程在啟動的過程中,會啟動另外一個進程ueventd來管理系統的設備文件。當ueventd進程啟動起來之後,會通過netlink接口來Linux內核通信,以便可以獲得內核中的硬件設備變化通知。而當ueventd進程發現內核中創建了一個類型和名稱分別為“graphics”和“fb0”的設備的時候,就會這個設備創建一個/dev/graphics/fb0設備文件。這樣,用戶空間的應用程序就可以通過設備文件/dev/graphics/fb0來訪問內核中的幀緩沖區,即在設備的顯示屏中繪制指定的畫面。注意,用戶空間的應用程序一般是通過內存映射的方式來訪問設備文件/dev/graphics/fb0的。
Android系統定義了硬件抽象層模塊的編寫規范,具體可以參考Android硬件抽象層(HAL)概要介紹和學習計劃一文。本文假設讀者已經熟悉Android系統的硬件抽象層編寫規范,因此,我們將按照幀緩沖區的使用情景以及硬件抽象層編寫規范來介紹Gralloc模塊的實現。
用戶空間的應用程序在使用幀緩沖區之間,首先要加載Gralloc模塊,並且獲得一個gralloc設備和一個fb設備。有了gralloc設備之後,用戶空間中的應用程序就可以申請分配一塊圖形緩沖區,並且將這塊圖形緩沖區映射到應用程序的地址空間來,以便可以向裡面寫入要繪制的畫面的內容。最後,用戶空間中的應用程序就通過fb設備來將前面已經准備好了的圖形緩沖區渲染到幀緩沖區中去,即將圖形緩沖區的內容繪制到顯示屏中去。相應地,當用戶空間中的應用程序不再需要使用一塊圖形緩沖區的時候,就可以通過gralloc設備來釋放它,並且將它從地址空間中解除映射。接下來,我們就按照上述使用情景來分析Gralloc模塊的實現。
1. Gralloc模塊的加載過程。
每一個HAL模塊都有一個ID值,以這些ID值為參數來調用硬件抽象層提供的函數hw_get_module就可以將指定的模塊加載到內存來,並且獲得一個hw_module_t接口來打開相應的設備。
Gralloc模塊的ID值定義在hardware/libhardware/include/hardware/gralloc.h文件中,如下所示:
[cpp]
#define GRALLOC_HARDWARE_MODULE_ID "gralloc"
函數hw_get_module實現在hardware/libhardware/hardware.c文件中,如下所示:
[cpp] view plaincopy
/** Base path of the hal modules */
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
/**
* There are a set of variant filename for modules. The form of the filename
* is "<MODULE_ID>.variant.so" so for the led module the Dream variants
* of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
*
* led.trout.so
* led.msm7k.so
* led.ARMV6.so
* led.default.so
*/
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
......
int hw_get_module(const char *id, const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys[i], prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH1, id, prop);
if (access(path, R_OK) == 0) break;
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH2, id, prop);
if (access(path, R_OK) == 0) break;
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH1, id);
if (access(path, R_OK) == 0) break;
}
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(id, path, module);
}
return status;
}
函數hw_get_module依次在目錄/system/lib/hw和/vendor/lib/hw中查找一個名稱為"<MODULE_ID>.variant.so"的文件,其中,<MODULE_ID>是一個模塊ID,而variant表示"ro.hardware"、"ro.product.board"、"ro.board.platform"和"ro.arch"四個系統屬性值之一。例如,對於Gralloc模塊來說,函數hw_get_module依次在目錄/system/lib/hw和/vendor/lib/hw中檢查是否存在以下四個文件:
gralloc.<ro.hardware>.so
gralloc.<ro.product.board>.so
gralloc.<ro.board.platform>.so
gralloc.<ro.arch>.so
只要其中的一個文件存在, 函數hw_get_module就會停止查找過程,並且調用另外一個函數load來將這個文件加載到內存中來。另一方面,如果在/system/lib/hw和/vendor/lib/hw中均不存這些文件,那麼函數hw_get_module就會在目錄/system/lib/hw中查找是否存在一個名稱為gralloc.default.so的文件。如果存在的話,那麼也會調用函數load將它加載到內存中來。
函數load也是實現在文件hardware/libhardware/hardware.c文件中,如下所示:
[cpp]
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
int status;
void *handle;
struct hw_module_t *hmi;
/*
* load the symbols resolving undefined symbols before
* dlopen returns. Since RTLD_GLOBAL is not or'd in with
* RTLD_NOW the external symbols will not be global
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
LOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
LOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/* Check that the id matches */
if (strcmp(id, hmi->id) != 0) {
LOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
}
hmi->dso = handle;
/* success */
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p",
id, path, *pHmi, handle);
}
*pHmi = hmi;
return status;
}
在Linux系統中,後綴名為"so"的文件為動態鏈接庫文件,可能通過函數dlopen來加載到內存中。硬件抽象層模塊編寫規范規定每一個硬件抽象層模塊都必須導出一個符號名稱為HAL_MODULE_INFO_SYM_AS_STR的符號,而且這個符號必須是用來描述一個類型為hw_module_t的結構體的。
HAL_MODULE_INFO_SYM_AS_STR是一個宏,定義在文件hardware/libhardware/include/hardware/hardware.h文件中,如下所示:
[cpp]
#define HAL_MODULE_INFO_SYM_AS_STR "HMI"
將Gralloc模塊加載到內存中來之後,就可以調用函數dlsym來獲得它所導出的符號HMI。由於這個符號指向的是一個hw_module_t結構體,因此,最後函數load就可以強制地將這個符號轉換為一個hw_module_t結構體指針,並且保存在輸出參數pHmi中返回給調用者。調用者獲得了這個hw_module_t結構體指針之後,就可以創建一個gralloc設備或者一個fb設備。
模塊Gralloc實現在目錄hardware/libhardware/modules/gralloc中,它導出的符號HMI定義在文件hardware/libhardware/modules/gralloc/gralloc.cpp文件中,如下所示:
[cpp]
static struct hw_module_methods_t gralloc_module_methods = {
open: gralloc_device_open
};
struct private_module_t HAL_MODULE_INFO_SYM = {
base: {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: GRALLOC_HARDWARE_MODULE_ID,
name: "Graphics Memory Allocator Module",
author: "The Android Open Source Project",
methods: &gralloc_module_methods
},
registerBuffer: gralloc_register_buffer,
unregisterBuffer: gralloc_unregister_buffer,
lock: gralloc_lock,
unlock: gralloc_unlock,
},
framebuffer: 0,
flags: 0,
numBuffers: 0,
bufferMask: 0,
lock: PTHREAD_MUTEX_INITIALIZER,
currentBuffer: 0,
};
HAL_MODULE_INFO_SYM也是一個宏,它的值是與宏HAL_MODULE_INFO_SYM_AS_STR對應的,它也是定義在文件hardware/libhardware/include/hardware/hardware.h文件中,如下所示:
[cpp] view plaincopy
#define HAL_MODULE_INFO_SYM HMI
符號HAL_MODULE_INFO_SYM的類型為private_module_t。前面提到,符號HAL_MODULE_INFO_SYM必須指向一個hw_module_t結構體,但是這裡它指向的卻是一個private_module_t結構體,是不是有問題呢?為了弄清楚這個問題,我們首先了解一下結構體private_module_t的定義,如圖1所示:
圖1 private_module_t結構體定義
結構體private_module_t的第一個成員變量base指向一個gralloc_module_t結構體,而gralloc_module_t結構體的第一個成員變量common又指向了一個hw_module_t結構體,這意味著,指向一個private_module_t結構體的指針同時可以用作一個gralloc_module_t或者hw_module_t結構體提針來使用。事實上,這是使用C語言來實現的一種繼承關系,等價於結構體private_module_t繼承結構體gralloc_module_t,而結構體gralloc_module_t繼承hw_module_t結構體。這樣,我們就可以把在Gralloc模塊中定義的符號HAL_MODULE_INFO_SYM看作是一個hw_module_t結構體。
hw_module_t結構體有一個重要的成員變量methods,它的類型為hw_module_methods_t,它用來描述一個HAL模塊的操作方法列表。結構體hw_module_methods_t只定義有一個操作方法open,用來打開一個指定的設備。在Gralloc模塊中,用來打開指定設備的函數被指定為gralloc_device_open,通過這個函數就可以打開Gralloc模塊中的gralloc或者fb設備,後面我們再詳細分析。
結構體gralloc_module_t定義在文件hardware/libhardware/include/hardware/gralloc.h中,它主要是定義了四個用來操作圖形緩沖區的成員函數,如下所示:
[cpp]
typedef struct gralloc_module_t {
......
int (*registerBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);
int (*unregisterBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);
int (*lock)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr);
int (*unlock)(struct gralloc_module_t const* module,
buffer_handle_t handle);
......
}
成員函數registerBuffer和unregisterBuffer分別用來注冊和注銷一個指定的圖形緩沖區,這個指定的圖形緩沖區使用一個buffer_handle_t句柄來描述。所謂注冊圖形緩沖區,實際上就是將一塊圖形緩沖區映射到一個進程的地址空間去,而注銷圖形緩沖區就是執行相反的操作。
成員函數lock和unlock分別用來鎖定和解鎖一個指定的圖形緩沖區,這個指定的圖形緩沖區同樣是使用一個buffer_handle_t句柄來描述。在訪問一塊圖形緩沖區的時候,例如,向一塊圖形緩沖寫入內容的時候,需要將該圖形緩沖區鎖定,用來避免訪問沖突。在鎖定一塊圖形緩沖區的時候,可以指定要鎖定的圖形繪沖區的位置以及大小,這是通過參數l、t、w和h來指定的,其中,參數l和t指定的是要訪問的圖形緩沖區的左上角位置,而參數w和h指定的是要訪問的圖形緩沖區的寬度和長度。鎖定之後,就可以獲得由參數參數l、t、w和h所圈定的一塊緩沖區的起始地址,保存在輸出參數vaddr中。另一方面,在訪問完成一塊圖形緩沖區之後,需要解除這塊圖形緩沖區的鎖定。
在Gralloc模塊中,符號HAL_MODULE_INFO_SYM指向的gralloc結構體的成員函數registerBuffer、unregisterBuffer、lock和unlock分別被指定為函數gralloc_register_buffer、gralloc_unregister_buffer、gralloc_lock和gralloc_unlock,後面我們再詳細分析它們的實現。
結構體private_module_t定義在文件hardware/libhardware/modules/gralloc/gralloc_priv.h中,它主要是用來描述幀緩沖區的屬性,如下所示:
[cpp]
struct private_module_t {
gralloc_module_t base;
private_handle_t* framebuffer;
uint32_t flags;
uint32_t numBuffers;
uint32_t bufferMask;
pthread_mutex_t lock;
buffer_handle_t currentBuffer;
int pmem_master;
void* pmem_master_base;
struct fb_var_screeninfo info;
struct fb_fix_screeninfo finfo;
float xdpi;
float ydpi;
float fps;
};
成員變量framebuffer的類型為private_handle_t,它是一個指向系統幀緩沖區的句柄,後面我們再分析結構體private_handle_t的定義。
成員變量flags用來標志系統幀緩沖區是否支持雙緩沖。如果支持的話,那麼它的PAGE_FLIP位就等於1,否則的話,就等於0。
成員變量numBuffers表示系統幀緩沖區包含有多少個圖形緩沖區。一個幀緩沖區包含有多少個圖形緩沖區是與它的可視分辨率以及虛擬分辨率的大小有關的。例如,如果一個幀緩沖區的可視分辨率為800 x 600,而虛擬分辨率為1600 x 600,那麼這個幀緩沖區就可以包含有兩個圖形緩沖區。
成員變量bufferMask用來記錄系統幀緩沖區中的圖形緩沖區的使用情況。例如,假設系統幀緩沖區有兩個圖形緩沖區,這時候成員變量bufferMask就有四種取值,分別是二進制的00、01、10和11,其中,00分別表示兩個圖緩沖區都是空閒的,01表示第1個圖形緩沖區已經分配出去,而第2個圖形緩沖區是空閒的,10表示第1個圖形緩沖區是空閒的,而第2個圖形緩沖區已經分配出去,11表示兩個圖緩沖區都已經分配出去。
成員變量lock是一個互斥鎖,用來保護結構體private_module_t的並行訪問。
成員變量currentBuffer的類型為buffer_handle_t,用來描述當前正在被渲染的圖形緩沖區,後面我們再分析它的定義。
成員變量pmem_master和pmem_master_base目前沒有使用。
成員變量info和finfo的類型分別為fb_var_screeninfo和fb_fix_screeninfo,它們用來保存設備顯示屏的屬性信息,其中,成員變量info保存的屬性信息是可以動態設置的,而成員變量finfo保存的屬性信息是只讀的。這兩個成員變量的值可以通過IO控制命令FBIOGET_VSCREENINFO和FBIOGET_FSCREENINFO來從幀緩沖區驅動模塊中獲得。
成員變量xdpi和ydpi分別用來描述設備顯示屏在寬度和高度上的密度,即每英寸有多少個像素點。
成員變量fps用來描述顯示屏的刷新頻率,它的單位的fps,即每秒幀數。
接下來, 我們再分析結構體buffer_handle_t和private_handle_t的定義。
結構體buffer_handle_t定義在文件hardware/libhardware/include/hardware/gralloc.h文件中,如下所示:
[cpp]
typedef const native_handle* buffer_handle_t;
它是一個類型為native_handle_t的指針,而結構體native_handle_t用來描述一個本地句柄值,它定義在系統運行時層的文件system/core/include/cutils/native_handle.h文件中,如下所示:
[cpp] view plaincopy
typedef struct
{
int version; /* sizeof(native_handle_t) */
int numFds; /* number of file-descriptors at &data[0] */
int numInts; /* number of ints at &data[numFds] */
int data[0]; /* numFds + numInts ints */
} native_handle_t;
成員變量version的大小被設置為結構體native_handle_t的大小,用來標識結構體native_handle_t的版本。
成員變量numFds和numInts表示結構體native_handle_t所包含的文件描述符以及整數值的個數,這些文件描述符和整數保存在成員變量data所指向的一塊緩沖區中。
我們一般不直接使用native_handle_t結構體來描述一個本地句柄值,而是通過它的子類來描述一個具體的本地句柄值。接下來我們就通過結構體private_handle_t的定義來說明native_handle_t結構體的用法。
結構體private_handle_t用來描述一塊圖形緩沖區,這塊圖形緩沖區可能是在幀緩沖區中分配的,也可能是在內存中分配的,視具體情況而定,它定義在文件hardware/libhardware/modules/gralloc/gralloc_priv.h文件中,如下所示:
[cpp]
#ifdef __cplusplus
struct private_handle_t : public native_handle {
#else
struct private_handle_t {
struct native_handle nativeHandle;
#endif
enum {
PRIV_FLAGS_FRAMEBUFFER = 0x00000001
};
// file-descriptors
int fd;
// ints
int magic;
int flags;
int size;
int offset;
// FIXME: the attributes below should be out-of-line
int base;
int pid;
#ifdef __cplusplus
static const int sNumInts = 6;
static const int sNumFds = 1;
static const int sMagic = 0x3141592;
private_handle_t(int fd, int size, int flags) :
fd(fd), magic(sMagic), flags(flags), size(size), offset(0),
base(0), pid(getpid())
{
version = sizeof(native_handle);
numInts = sNumInts;
numFds = sNumFds;
}
~private_handle_t() {
magic = 0;
}
static int validate(const native_handle* h) {
const private_handle_t* hnd = (const private_handle_t*)h;
if (!h || h->version != sizeof(native_handle) ||
h->numInts != sNumInts || h->numFds != sNumFds ||
hnd->magic != sMagic)
{
LOGE("invalid gralloc handle (at %p)", h);
return -EINVAL;
}
return 0;
}
#endif
};
為了方便描述,我們假設我們是在C++環境中編譯文件gralloc_priv.h,即編譯環境定義有宏__cplusplus。這樣,結構體private_handle_t就是從結構體native_handle_t繼承下來的,它包含有1個文件描述符以及6個整數,以及三個靜態成員變量。
成員變量fd指向一個文件描述符,這個文件描述符要麼指向幀緩沖區設備,要麼指向一塊匿名共享內存,取決於它的宿主結構體private_handle_t描述的一個圖形緩沖區是在幀緩沖區分配的,還是在內存中分配的。
成員變量magic指向一個魔數,它的值由靜態成員變量sMagic來指定,用來標識一個private_handle_t結構體。
成員變量flags用來描述一個圖形緩沖區的標志,它的值要麼等於0,要麼等於PRIV_FLAGS_FRAMEBUFFER。當一個圖形緩沖區的標志值等於PRIV_FLAGS_FRAMEBUFFER的時候,就表示它是在幀緩沖區中分配的。
成員變量size用來描述一個圖形緩沖區的大小。
成員變量offset用來描述一個圖形緩沖區的偏移地址。例如,當一個圖形緩沖區是在一塊內存中分塊的時候,假設這塊內存的地址為start,那麼這個圖形緩沖區的起始地址就為start + offset。
成員變量base用來描述一個圖形緩沖區的實際地址,它是通過成員變量offset來計算得到的。例如,上面計算得到的start + offset的值就保存在成員變量base中。
成員變量pid用來描述一個圖形緩沖區的創建者的PID。例如,如果一個圖形緩沖區是在ID值為1000的進程中創建的,那麼用來描述這個圖形緩沖區的private_handle_t結構體的成員變量pid的值就等於1000。
結構體private_handle_t的靜態成員變量sMagic前面已經描述過了,另外兩個靜態成員變量sNumInts和sNumFds的值分別等於1和6,表示結構體private_handle_t包含有1個文件描述符和6個整數,它們是用來初始化結構體private_handle_t的父類native_handle_t的成員變量numInts和numFds的,如結構體private_handle_t的構造函數所示。從這裡就可以看出,結構體private_handle_t的父類native_handle_t的成員變量data所指向的緩沖區就是由結構體private_handle_t的成員變量fds、magic、flags、size、offset、base和pid所占用的連續內存塊來組成的,一共包含有7個整數。
結構體private_handle_t還定義了一個靜態成員函數validate,用來驗證一個native_handle_t指針是否指向了一個private_handle_t結構體。
至此,Gralloc模塊的加載過程以及相關的數據結構體就介紹到這裡,接下來我們分別分析定義在Gralloc模塊中的gralloc和fb設備的打開過程。
2. gralloc設備的打開過程
在Gralloc模塊中,gralloc設備的ID值定義為GRALLOC_HARDWARE_GPU0。GRALLOC_HARDWARE_GPU0是一個宏,定義在文件hardware/libhardware/include/hardware/gralloc.h中, 如下所示:
[cpp]
#define GRALLOC_HARDWARE_GPU0 "gpu0"
gralloc設備使用結構體alloc_device_t 來描述。結構體alloc_device_t有兩個成員函數alloc和free,分別用來分配和釋放圖形緩沖區。
結構體alloc_device_t 也是定義在文件hardware/libhardware/include/hardware/gralloc.h中, 如下所示:
[cpp]
typedef struct alloc_device_t {
struct hw_device_t common;
int (*alloc)(struct alloc_device_t* dev,
int w, int h, int format, int usage,
buffer_handle_t* handle, int* stride);
int (*free)(struct alloc_device_t* dev,
buffer_handle_t handle);
} alloc_device_t;
Gralloc模塊在在文件hardware/libhardware/include/hardware/gralloc.h中定義了一個幫助函數gralloc_open,用來打開gralloc設備,如下所示:
[cpp]
static inline int gralloc_open(const struct hw_module_t* module,
struct alloc_device_t** device) {
return module->methods->open(module,
GRALLOC_HARDWARE_GPU0, (struct hw_device_t**)device);
}
參數module指向的是一個用來描述Gralloc模塊的hw_module_t結構體,它的成員變量methods所指向的一個hw_module_methods_t結構體的成員函數open指向了Gralloc模塊中的函數gralloc_device_open。
函數gralloc_device_open定義在文件hardware/libhardware/modules/gralloc/gralloc.cpp文件中,如下所示:
[cpp]
struct gralloc_context_t {
alloc_device_t device;
/* our private data here */
};
......
int gralloc_device_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
gralloc_context_t *dev;
dev = (gralloc_context_t*)malloc(sizeof(*dev));
/* initialize our state here */
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = gralloc_close;
dev->device.alloc = gralloc_alloc;
dev->device.free = gralloc_free;
*device = &dev->device.common;
status = 0;
}
......
return status;
}
這個函數主要是用來創建一個gralloc_context_t結構體,並且對它的成員變量device進行初始化。結構體gralloc_context_t的成員變量device的類型為gralloc_device_t,它用來描述一個gralloc設備。前面提到,gralloc設備是用來分配和釋放圖形緩沖區的,這是通過調用它的成員函數alloc和free來實現的。從這裡可以看出,函數gralloc_device_open所打開的gralloc設備的成員函數alloc和free分別被設置為Gralloc模塊中的函數gralloc_alloc和gralloc_free,後面我們再詳細分析它們的實現。
至此,gralloc設備的打開過程就分析完成了,接下來我們繼續分析fb設備的打開過程。
3. fb設備的打開過程
在Gralloc模塊中,fb設備的ID值定義為GRALLOC_HARDWARE_FB0。GRALLOC_HARDWARE_FB0是一個宏,定義在文件hardware/libhardware/include/hardware/gralloc.h中, 如下所示:
[cpp]
#define GRALLOC_HARDWARE_FB0 "fb0"
fb設備使用結構體framebuffer_device_t 來描述。結構體framebuffer_device_t是用來描述系統幀緩沖區的信息,它定義在文件hardware/libhardware/include/hardware/gralloc.h中, 如下所示:
[cpp]
typedef struct framebuffer_device_t {
struct hw_device_t common;
/* flags describing some attributes of the framebuffer */
const uint32_t flags;
/* dimensions of the framebuffer in pixels */
const uint32_t width;
const uint32_t height;
/* frambuffer stride in pixels */
const int stride;
/* framebuffer pixel format */
const int format;
/* resolution of the framebuffer's display panel in pixel per inch*/
const float xdpi;
const float ydpi;
/* framebuffer's display panel refresh rate in frames per second */
const float fps;
/* min swap interval supported by this framebuffer */
const int minSwapInterval;
/* max swap interval supported by this framebuffer */
const int maxSwapInterval;
int reserved[8];
int (*setSwapInterval)(struct framebuffer_device_t* window,
int interval);
int (*setUpdateRect)(struct framebuffer_device_t* window,
int left, int top, int width, int height);
int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);
int (*compositionComplete)(struct framebuffer_device_t* dev);
void* reserved_proc[8];
} framebuffer_device_t;
成員變量flags用來記錄系統幀緩沖區的標志,目前沒有使用這成員變量,它的值被設置為0。
成員變量width和height分別用來描述設備顯示屏的寬度和高度,它們是以像素為單位的。
成員變量stride用來描述設備顯示屏的一行有多少個像素點。
成員變量format用來描述系統幀緩沖區的像素格式,支持的像素格式主要有HAL_PIXEL_FORMAT_RGBX_8888和HAL_PIXEL_FORMAT_RGB_565兩種。HAL_PIXEL_FORMAT_RGBX_8888表示一個像素使用32位來描述,R、G和B分別占8位,另外8位未使用。HAL_PIXEL_FORMAT_RGB_565表示一個像素使用16位來描述,R、G和B分別占5、6和5位。
成員變量xdpi和ydpi分別用來描述設備顯示屏在寬度和高度上的密度,即每英寸有多少個像素點。
成員變量fps用來描述設備顯示屏的刷新頻率,它的單位是幀每秒。
成員變量minSwapInterval和maxSwapInterval用來描述幀緩沖區交換前後兩個圖形緩沖區的最小和最大時間間隔。
成員變量reserved是保留給將來使用的。
成員函數setSwapInterval用來設置幀緩沖區交換前後兩個圖形緩沖區的最小和最大時間間隔。
成員函數setUpdateRect用來設置幀緩沖區的更新區域。
成員函數post用來將圖形緩沖區buffer的內容渲染到幀緩沖區中去,即顯示在設備的顯示屏中去。
成員函數compositionComplete用來通知fb設備device,圖形緩沖區的組合工作已經完成,目前沒有使用這個成員函數。
成員變量reserved是一個函數指針數組,它們是保留給將來使用的。
在結構體framebuffer_device_t的一系列成員函數中,post是最重要的一個成員函數,用戶空間的應用程序通過調用這個成員函數就可以在設備的顯示屏中渲染指定的畫面,後面我們將詳細這個函數的實現。
Gralloc模塊在在文件hardware/libhardware/include/hardware/gralloc.h中定義了一個幫助函數framebuffer_open,用來打開fb設備,如下所示:
[cpp]
static inline int framebuffer_open(const struct hw_module_t* module,
struct framebuffer_device_t** device) {
return module->methods->open(module,
GRALLOC_HARDWARE_FB0, (struct hw_device_t**)device);
}
參數module指向的是一個用來描述Gralloc模塊的hw_module_t結構體,前面提到,它的成員變量methods所指向的一個hw_module_methods_t結構體的成員函數open指向了Gralloc模塊中的函數gralloc_device_open,這個函數打開fb設備的代碼段如下所示:
[cpp]
int gralloc_device_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
......
} else {
status = fb_device_open(module, name, device);
}
return status;
}
參數name的值等於GRALLOC_HARDWARE_FB0,因此,函數gralloc_device_open接下來會調用另外一個函數fb_device_open來執行打開fb設備的操作。
函數fb_device_open定義在文件hardware/libhardware/modules/gralloc/framebuffer.cpp中,如下所示:
[cpp]
struct fb_context_t {
framebuffer_device_t device;
};
......
int fb_device_open(hw_module_t const* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
alloc_device_t* gralloc_device;
status = gralloc_open(module, &gralloc_device);
if (status < 0)
return status;
/* initialize our state here */
fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = fb_close;
dev->device.setSwapInterval = fb_setSwapInterval;
dev->device.post = fb_post;
dev->device.setUpdateRect = 0;
private_module_t* m = (private_module_t*)module;
status = mapFrameBuffer(m);
if (status >= 0) {
int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3);
int format = (m->info.bits_per_pixel == 32)
? HAL_PIXEL_FORMAT_RGBX_8888
: HAL_PIXEL_FORMAT_RGB_565;
#ifdef NO_32BPP
format = HAL_PIXEL_FORMAT_RGB_565;
#endif
const_cast<uint32_t&>(dev->device.flags) = 0;
const_cast<uint32_t&>(dev->device.width) = m->info.xres;
const_cast<uint32_t&>(dev->device.height) = m->info.yres;
const_cast<int&>(dev->device.stride) = stride;
const_cast<int&>(dev->device.format) = format;
const_cast<float&>(dev->device.xdpi) = m->xdpi;
const_cast<float&>(dev->device.ydpi) = m->ydpi;
const_cast<float&>(dev->device.fps) = m->fps;
const_cast<int&>(dev->device.minSwapInterval) = 1;
const_cast<int&>(dev->device.maxSwapInterval) = 1;
*device = &dev->device.common;
}
}
return status;
}
這個函數主要是用來創建一個fb_context_t結構體,並且對它的成員變量device進行初始化。結構體fb_context_t的成員變量device的類型為framebuffer_device_t,前面提到,它是用來描述fb設備的。fb設備主要是用來渲染圖形緩沖區的,這是通過調用它的成員函數post來實現的。從這裡可以看出,函數fb_device_open所打開的fb設備的成員函數post被設置為Gralloc模塊中的函數fb_post,後面我們再詳細分析它的實現。
函數fb_device_open在打開fb設備的過程中,會調用另外一個函數mapFrameBuffer來獲得系統幀緩沖區的信息,並且將這些信息保存在參數module所描述的一個private_module_t結構體的各個成員變量中。有了系統幀緩沖區的信息之後,函數fb_device_open接下來就可以對前面所打開的一個fb設備的各個成員變量進行初始化。這些成員變量的含義可以參考前面對結構體framebuffer_device_t的介紹。接下來我們只簡單介紹一下結構體framebuffer_device_t的成員變量stride和format的初始化過程。
變量m的成員變量finfo的類型為fb_fix_screeninfo,它是在函數mapFrameBuffer中被始化的。fb_fix_screeninfo是在內核中定義的一個結構體,用來描述設備顯示屏的固定屬性信息,其中,它的成員變量line_length用來描述顯示屏一行像素總共所占用的字節數。
變量m的另外一個成員變量info的類型為fb_var_screeninfo,它也是在函數mapFrameBuffer中被始化的。fb_var_screeninfo也是內核中定義的一個結構體,用來描述可以動態設置的顯示屏屬性信息,其中,它的成員變量bits_per_pixel用來描述顯示屏每一個像素所占用的位數。
這樣,我們將m->info.bits_per_pixel的值向右移3位,就可以得到顯示屏每一個像素所占用的字節數。用顯示屏每一個像素所占用的字節數去除顯示屏一行像素總共所占用的字節數m->finfo.line_length,就可以得到顯示屏一行有多少個像素點。這個值最終就可以保存在前面所打開的fb設備的成員變量stride中。
當顯示屏每一個像素所占用的位數等於32的時候,那麼前面所打開的fb設備的像素格式format就會被設置為HAL_PIXEL_FORMAT_RGBX_8888,否則的話,就會被設置為HAL_PIXEL_FORMAT_RGB_565。另一方面,如果在編譯的時候定義了NO_32BPP宏,即不要使用32位來描述一個像素,那麼函數fb_device_open就會強制將前面所打開的fb設備的像素格式format設置為HAL_PIXEL_FORMAT_RGB_565。
函數mapFrameBuffer除了用來獲得系統幀緩沖區的信息之外,還會將系統幀緩沖區映射到當前進程的地址空間來。在Android系統中,Gralloc模塊中的fb設備是由SurfaceFlinger服務來負責打開和管理的,而SurfaceFlinger服是運行System進程中的,因此,系統幀緩沖區實際上是映射到System進程的地址空間中的。
函數mapFrameBuffer實現在文件hardware/libhardware/modules/gralloc/framebuffer.cpp,如下所示:
[cpp]
static int mapFrameBuffer(struct private_module_t* module)
{
pthread_mutex_lock(&module->lock);
int err = mapFrameBufferLocked(module);
pthread_mutex_unlock(&module->lock);
return err;
}
這個函數調用了同一個文件中的另外一個函數mapFrameBufferLocked來初始化參數module以及將系統幀緩沖區映射到當前進程的地址空間來。
函數mapFrameBufferLocked的實現比較長,我們分段來閱讀:
[cpp]
int mapFrameBufferLocked(struct private_module_t* module)
{
// already initialized...
if (module->framebuffer) {
return 0;
}
char const * const device_template[] = {
"/dev/graphics/fb%u",
"/dev/fb%u",
0 };
int fd = -1;
int i=0;
char name[64];
while ((fd==-1) && device_template[i]) {
snprintf(name, 64, device_template[i], 0);
fd = open(name, O_RDWR, 0);
i++;
}
if (fd < 0)
return -errno;
這段代碼在首先在系統中檢查是否存在設備文件/dev/graphics/fb0或者/dev/fb0。如果存在的話,那麼就調用函數open來打開它,並且將得到的文件描述符保存在變量fd中。這樣,接下來函數mapFrameBufferLocked就可以通過文件描述符fd來與內核中的幀緩沖區驅動程序交互。
繼續往下看函數mapFrameBufferLocked:
[cpp]
struct fb_fix_screeninfo finfo;
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
return -errno;
struct fb_var_screeninfo info;
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
這幾行代碼分別通過IO控制命令FBIOGET_FSCREENINFO和FBIOGET_VSCREENINFO來獲得系統幀緩沖區的信息,分別保存在fb_fix_screeninfo結構體finfo和fb_var_screeninfo結構體info中。
再往下看函數mapFrameBufferLocked:
[cpp]
info.reserved[0] = 0;
info.reserved[1] = 0;
info.reserved[2] = 0;
info.xoffset = 0;
info.yoffset = 0;
info.activate = FB_ACTIVATE_NOW;
#if defined(NO_32BPP)
/*
* Explicitly request 5/6/5
*/
info.bits_per_pixel = 16;
info.red.offset = 11;
info.red.length = 5;
info.green.offset = 5;
info.green.length = 6;
info.blue.offset = 0;
info.blue.length = 5;
info.transp.offset = 0;
info.transp.length = 0;
#endif
/*
* Request NUM_BUFFERS screens (at lest 2 for page flipping)
*/
info.yres_virtual = info.yres * NUM_BUFFERS;
uint32_t flags = PAGE_FLIP;
if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) {
info.yres_virtual = info.yres;
flags &= ~PAGE_FLIP;
LOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported");
}
if (info.yres_virtual < info.yres * 2) {
// we need at least 2 for page-flipping
info.yres_virtual = info.yres;
flags &= ~PAGE_FLIP;
LOGW("page flipping not supported (yres_virtual=%d, requested=%d)",
info.yres_virtual, info.yres*2);
}
這段代碼主要是用來設置設備顯示屏的虛擬分辨率。在前面Android系統的開機畫面顯示過程分析一文提到,結構體fb_var_screeninfo的成員變量xres和yres用來描述顯示屏的可視分辨率,而成員變量xres_virtual和yres_virtual用來描述顯示屏的虛擬分辨率。這裡保持可視分辨率以及虛擬分辨率的寬度值不變,而將虛擬分辨率的高度值設置為可視分辨率的高度值的NUM_BUFFERS倍。NUM_BUFFERS是一個宏,它的值被定義為2。這樣,我們就可以將系統幀緩沖區劃分為兩個圖形緩沖區來使用,即可以通過硬件來實現雙緩沖技術。
在結構體fb_var_screeninfo中,與顯示屏的可視分辨率和虛擬分辨率相關的另外兩個成員變量是xoffset和yoffset,它們用來告訴幀緩沖區當前要渲染的圖形緩沖區是哪一個,它們的使用方法可以參考前面Android系統的開機畫面顯示過程分析一文。
這段代碼在設置設備顯示屏的虛擬分辨率之前,還會檢查是否定義了宏NO_32BPP。如果定義了的話,那麼就說明系統顯式地要求將幀緩沖區的像素格式設置為HAL_PIXEL_FORMAT_RGB_565。在這種情況下,這段代碼就會通過fb_var_screeninfo結構體info的成員變量bits_per_pixel、red、green、blue和transp來通知幀緩沖區驅動程序使用HAL_PIXEL_FORMAT_RGB_565像素格式來渲染顯示屏。
這段代碼最終是通過IO控制命令FBIOPUT_VSCREENINFO來設置設備顯示屏的虛擬分辨率以及像素格式的。如果設置失敗,即調用函數ioctl的返回值等於-1,那麼很可能是因為系統幀緩沖區在硬件上不支持雙緩沖,因此,接下來的代碼就會重新將顯示屏的虛擬分辨率的高度值設置為可視分辨率的高度值,並且將變量flags的PAGE_FLIP位置為0。
另一方面,如果調用函數ioctl成功,但是最終獲得的顯示屏的虛擬分辨率的高度值小於可視分辨率的高度值的2倍,那麼也說明系統幀緩沖區在硬件上不支持雙緩沖。在這種情況下,接下來的代碼也會重新將顯示屏的虛擬分辨率的高度值設置為可視分辨率的高度值,並且將變量flags的PAGE_FLIP位置為0。
再繼續往下看函數mapFrameBufferLocked:
[cpp]
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
uint64_t refreshQuotient =
(
uint64_t( info.upper_margin + info.lower_margin + info.yres )
* ( info.left_margin + info.right_margin + info.xres )
* info.pixclock
);
/* Beware, info.pixclock might be 0 under emulation, so avoid a
* division-by-0 here (SIGFPE on ARM) */
int refreshRate = refreshQuotient > 0 ? (int)(1000000000000000LLU / refreshQuotient) : 0;
if (refreshRate == 0) {
// bleagh, bad info from the driver
refreshRate = 60*1000; // 60 Hz
}
這段代碼再次通過IO控制命令FBIOGET_VSCREENINFO來獲得系統幀緩沖區的可變屬性信息,並且保存在fb_var_screeninfo結構體info中,接下來再計算設備顯示屏的刷新頻率。
顯示屏的刷新頻率與顯示屏的掃描時序相關。顯示屏的掃描時序可以參考Linux內核源代碼目錄下的Documentation/fb/framebuffer.txt文件。我們結合圖2來簡單說明上述代碼是如何計算顯示屏的刷新頻率的。
圖 2 顯示屏掃描時序示意圖
中間由xres和yres組成的區域即為顯示屏的圖形繪制區,在繪制區的上、下、左和右分別有四個邊距upper_margin、lower_margin、left_margin和right_margin。此外,在顯示屏的最右邊以及最下邊還有一個水平同步區域hsync_len和一個垂直同步區域vsync_len。電子槍按照從左到右、從上到下的順序來顯示屏中打點,從而可以將要渲染的圖形顯示在屏幕中。前面所提到的區域信息分別保存在fb_var_screnninfo結構體info的成員變量xres、yres、upper_margin、lower_margin、left_margin、right_margin、hsync_len和vsync_len。
電子槍每在xres和yres所組成的區域中打一個點所花費的時間記錄在fb_var_screnninfo結構體info的成員變量pixclock,單位為pico seconds,即10E-12秒。
電子槍從左到右掃描完成一行之後,都會處理關閉狀態,並且會重新折回到左邊去。由於電子槍在從右到左折回的過程中不需要打點,因此,這個過程會比從左到右掃描屏幕的過程要快,這個折回的時間大概就等於在xres和yres所組成的區域掃描(left_margin+right_margin)個點的時間。這樣,我們就可以認為每渲染一行需要的時間為(xres + left_margin + right_margin)* pixclock。
同樣,電子槍從上到下掃描完成顯示屏之後,需要從右下角折回到左上角去,折回的時間大概等於在xres和yres所組成的區域中掃描(upper_margin + lower_margin)行所需要的時間。這樣,我們就可以認為每渲染一屏圖形所需要的時間等於在xres和yres所組成的區域中掃描(yres + upper_margin + lower_margin)行所需要的時間。由於在xres和yres所組成的區域中掃描一行所需要的時間為(xres + left_margin + right_margin)* pixclock,因此,每渲染一屏圖形所需要的總時間就等於(yres + upper_margin + lower_margin)* (xres + left_margin + right_margin)* pixclock。
每渲染一屏圖形需要的總時間經過計算之後,就保存在變量refreshQuotient中。注意,變量refreshQuotient所描述的時間的單位為1E-12秒。這樣,將變量refreshQuotient的值倒過來,就可以得到設備顯示屏的刷新頻率。將這個頻率值乘以10E15次方之後,就得到一個單位為10E-3 HZ的刷新頻率,保存在變量refreshRate中。
當Android系統在模擬器運行的時候,保存在fb_var_screnninfo結構體info的成員變量pixclock中的值可能等於0。在這種情況下,前面計算得到的變量refreshRate的值就會等於0。在這種情況下,接下來的代碼會將變量refreshRate的值設置為60 * 1000 * 10E-3 HZ,即將顯示屏的刷新頻率設置為60HZ。
再往下看函數mapFrameBufferLocked:
[cpp]
if (int(info.width) <= 0 || int(info.height) <= 0) {
// the driver doesn't return that information
// default to 160 dpi
info.width = ((info.xres * 25.4f)/160.0f + 0.5f);
info.height = ((info.yres * 25.4f)/160.0f + 0.5f);
}
float xdpi = (info.xres * 25.4f) / info.width;
float ydpi = (info.yres * 25.4f) / info.height;
float fps = refreshRate / 1000.0f;
這段代碼首先計算顯示屏的密度,即每英寸有多少個像素點,分別寬度和高度兩個維度,分別保存在變量xdpi和ydpi中。注意,fb_var_screeninfo結構體info的成員變量width和height用來描述顯示屏的寬度和高度,它們是以毫米(mm)為單位的。
這段代碼接著再將前面計算得到的顯示屏刷新頻率的單位由10E-3 HZ轉換為HZ,即幀每秒,並且保存在變量fps中。
再往下看函數mapFrameBufferLocked:
[cpp]
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
return -errno;
if (finfo.smem_len <= 0)
return -errno;
module->flags = flags;
module->info = info;
module->finfo = finfo;
module->xdpi = xdpi;
module->ydpi = ydpi;
module->fps = fps;
這段代碼再次通過IO控制命令FBIOGET_FSCREENINFO來獲得系統幀緩沖區的固定信息,並且保存在fb_fix_screeninfo結構體finfo中,接下來再使用fb_fix_screeninfo結構體finfo以及前面得到的系統幀緩沖區的其它信息來初始化參數module所描述的一個private_module_t結構體。
最後,函數mapFrameBufferLocked就將系統幀緩沖區映射到當前進程的地址空間來:
[cpp]
/*
* map the framebuffer
*/
int err;
size_t fbSize = roundUpToPageSize(finfo.line_length * info.yres_virtual);
module->framebuffer = new private_handle_t(dup(fd), fbSize, 0);
module->numBuffers = info.yres_virtual / info.yres;
module->bufferMask = 0;
void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (vaddr == MAP_FAILED) {
LOGE("Error mapping the framebuffer (%s)", strerror(errno));
return -errno;
}
module->framebuffer->base = intptr_t(vaddr);
memset(vaddr, 0, fbSize);
return 0;
}
表達式finfo.line_length * info.yres_virtual計算的是整個系統幀緩沖區的大小,它的值等於顯示屏行數(虛擬分辨率的高度值,info.yres_virtual)乘以每一行所占用的字節數(finfo.line_length)。函數roundUpToPageSize用來將整個系統幀緩沖區的大小對齊到頁面邊界。對齊後的大小保存在變量fbSize中。
表達式finfo.yres_virtual / info.yres計算的是整個系統幀緩沖區可以劃分為多少個圖形緩沖區來使用,這個數值保存在參數module所描述的一個private_module_t結構體的成員變量nmBuffers中。參數module所描述的一個private_module_t結構體的另外一個成員變量bufferMask的值接著被設置為0,表示系統幀緩沖區中的所有圖形緩沖區都是處於空閒狀態,即它們可以分配出去給應用程序使用。
系統幀緩沖區是通過調用函數mmap來映射到當前進程的地址空間來的。映射後得到的地址空間使用一個private_handle_t結構體來描述,這個結構體的成員變量base保存的即為系統幀緩沖區在當前進程的地址空間中的起始地址。這樣,Gralloc模塊以後就可以從這塊地址空間中分配圖形緩沖區給當前進程使用。
至此,fb設備的打開過程就分析完成了。在打開fb設備的過程中,Gralloc模塊還完成了對系統幀緩沖區的初始化工作。接下來我們繼續分析Gralloc模塊是如何分配圖形緩沖區給用戶空間的應用程序使用的。
4. 分配圖形緩沖區
前面提到,用戶空間的應用程序用到的圖形緩沖區是由Gralloc模塊中的函數gralloc_alloc來分配的,這個函數實現在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
[cpp]
static int gralloc_alloc(alloc_device_t* dev,
int w, int h, int format, int usage,
buffer_handle_t* pHandle, int* pStride)
{
if (!pHandle || !pStride)
return -EINVAL;
size_t size, stride;
int align = 4;
int bpp = 0;
switch (format) {
case HAL_PIXEL_FORMAT_RGBA_8888:
case HAL_PIXEL_FORMAT_RGBX_8888:
case HAL_PIXEL_FORMAT_BGRA_8888:
bpp = 4;
break;
case HAL_PIXEL_FORMAT_RGB_888:
bpp = 3;
break;
case HAL_PIXEL_FORMAT_RGB_565:
case HAL_PIXEL_FORMAT_RGBA_5551:
case HAL_PIXEL_FORMAT_RGBA_4444:
bpp = 2;
break;
default:
return -EINVAL;
}
size_t bpr = (w*bpp + (align-1)) & ~(align-1);
size = bpr * h;
stride = bpr / bpp;
int err;
if (usage & GRALLOC_USAGE_HW_FB) {
err = gralloc_alloc_framebuffer(dev, size, usage, pHandle);
} else {
err = gralloc_alloc_buffer(dev, size, usage, pHandle);
}
if (err < 0) {
return err;
}
*pStride = stride;
return 0;
}
參數format用來描述要分配的圖形緩沖區的顏色格式。當format值等於HAL_PIXEL_FORMAT_RGBA_8888、HAL_PIXEL_FORMAT_RGBX_8888或者HAL_PIXEL_FORMAT_BGRA_8888的時候,一個像素需要使用32位來表示,即4個字節。當format值等於HAL_PIXEL_FORMAT_RGB_888的時候,一個像素需要使用24位來描述,即3個字節。當format值等於HAL_PIXEL_FORMAT_RGB_565、HAL_PIXEL_FORMAT_RGBA_5551或者HAL_PIXEL_FORMAT_RGBA_4444的時候,一個像需要使用16位來描述,即2個字節。最終一個像素需要使用的字節數保存在變量bpp中。
參數w表示要分配的圖形緩沖區所保存的圖像的寬度,將它乘以bpp,就可以得到保存一行像素所需要使用的字節數。我們需要將這個字節數對齊到4個字節邊界,最後得到一行像素所需要的字節數就保存在變量bpr中。
參數h表示要分配的圖形緩沖區所保存的圖像的高度,將它乘以bpr,就可以得到保存整個圖像所需要使用的字節數。
將變量bpr的值除以變量bpp的值,就得到要分配的圖形緩沖區一行包含有多少個像素點,這個結果需要保存在輸出參數pStride中,以便可以返回給調用者。
參數usage用來描述要分配的圖形緩沖區的用途。如果是用來在系統幀緩沖區中渲染的,即參數usage的GRALLOC_USAGE_HW_FB位等於1,那麼就必須要系統幀緩沖區中分配,否則的話,就在內存中分配。注意,在內存中分配的圖形緩沖區,最終是需要拷貝到系統幀緩沖區去的,以便可以將它所描述的圖形渲染出來。
函數gralloc_alloc_framebuffer用來在系統幀緩沖區中分配圖形緩沖區,而函數gralloc_alloc_buffer用來在內存在分配圖形緩沖區,接下來我們就分別分析這兩個函數的實現。
函數gralloc_alloc_framebuffer實現在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
[cpp]
static int gralloc_alloc_framebuffer(alloc_device_t* dev,
size_t size, int usage, buffer_handle_t* pHandle)
{
private_module_t* m = reinterpret_cast<private_module_t*>(
dev->common.module);
pthread_mutex_lock(&m->lock);
int err = gralloc_alloc_framebuffer_locked(dev, size, usage, pHandle);
pthread_mutex_unlock(&m->lock);
return err;
}
這個函數調用了另外一個函數gralloc_alloc_framebuffer_locked來分配圖形緩沖區。
函數gralloc_alloc_framebuffer_locked也是實現在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
[cpp]
static int gralloc_alloc_framebuffer_locked(alloc_device_t* dev,
size_t size, int usage, buffer_handle_t* pHandle)
{
private_module_t* m = reinterpret_cast<private_module_t*>(
dev->common.module);
// allocate the framebuffer
if (m->framebuffer == NULL) {
// initialize the framebuffer, the framebuffer is mapped once
// and forever.
int err = mapFrameBufferLocked(m);
if (err < 0) {
return err;
}
}
const uint32_t bufferMask = m->bufferMask;
const uint32_t numBuffers = m->numBuffers;
const size_t bufferSize = m->finfo.line_length * m->info.yres;
if (numBuffers == 1) {
// If we have only one buffer, we never use page-flipping. Instead,
// we return a regular buffer which will be memcpy'ed to the main
// screen when post is called.
int newUsage = (usage & ~GRALLOC_USAGE_HW_FB) | GRALLOC_USAGE_HW_2D;
return gralloc_alloc_buffer(dev, bufferSize, newUsage, pHandle);
}
if (bufferMask >= ((1LU<<numBuffers)-1)) {
// We ran out of buffers.
return -ENOMEM;
}
// create a "fake" handles for it
intptr_t vaddr = intptr_t(m->framebuffer->base);
private_handle_t* hnd = new private_handle_t(dup(m->framebuffer->fd), size,
private_handle_t::PRIV_FLAGS_FRAMEBUFFER);
// find a free slot
for (uint32_t i=0 ; i<numBuffers ; i++) {
if ((bufferMask & (1LU<<i)) == 0) {
m->bufferMask |= (1LU<<i);
break;
}
vaddr += bufferSize;
}
hnd->base = vaddr;
hnd->offset = vaddr - intptr_t(m->framebuffer->base);
*pHandle = hnd;
return 0;
}
在系統幀緩沖區分配圖形緩沖區之前,首先要對系統幀緩沖區進行過初始化,即這裡的變量m所指向的一個private_module_t結構體的成員變量framebuffer的值不能等於NULL。如果等於NULL的話,那麼就必須要調用另外一個函數mapFrameBufferLocked來初始化系統幀緩沖區。初始化系統幀緩沖區的過程可以參考前面第3部分的內容。
變量bufferMask用來描述系統幀緩沖區的使用情況,而變量numBuffers用來描述系統幀緩沖區可以劃分為多少個圖形緩沖區來使用,另外一個變量bufferSize用來描述設備顯示屏一屏內容所占用的內存的大小。
如果系統幀緩沖區只有一個圖形緩沖區大小,即變量numBuffers的值等於1,那麼這個圖形緩沖區就始終用作系統主圖形緩沖區來使用。在這種情況下,我們就不能夠在系統幀緩沖區中分配圖形緩沖區來給用戶空間的應用程序使用,因此,這時候就會轉向內存中來分配圖形緩沖區,即調用函數gralloc_alloc_buffer來分配圖形緩沖區。注意,這時候分配的圖形緩沖區的大小為一屏內容的大小,即bufferSize。
如果bufferMask的值大於等於((1LU<<numBuffers)-1)的值,那麼就說明系統幀緩沖區中的圖形緩沖區全部都分配出去了,這時候分配圖形緩沖區就失敗了。例如,假設圖形緩沖區的個數為2,那麼((1LU<<numBuffers)-1)的值就等於3,即二制制0x11。如果這時候bufferMask的值也等於0x11,那麼就表示第一個和第二個圖形緩沖區都已經分配出去了。因此,這時候就不能再在系統幀緩沖區中分配圖形緩沖區。
假設此時系統幀緩沖區中尚有空閒的圖形緩沖區的,接下來函數就會創建一個private_handle_t結構體hnd來描述這個即將要分配出去的圖形緩沖區。注意,這個圖形緩沖區的標志值等於PRIV_FLAGS_FRAMEBUFFER,即表示這是一塊在系統幀緩沖區中分配的圖形緩沖區。
接下來的for循環從低位到高位檢查變量bufferMask的值,並且找到第一個值等於0的位,這樣就可以知道在系統幀緩沖區中,第幾個圖形緩沖區的是空閒的。注意,變量vadrr的值開始的時候指向系統幀緩沖區的基地址,在下面的for循環中,每循環一次它的值都會增加bufferSize。從這裡就可以看出,每次從系統幀緩沖區中分配出去的圖形緩沖區的大小都是剛好等於顯示屏一屏內容大小的。
最後分配出去的圖形緩沖區的開始地址就保存在前面所創建的private_handle_t結構體hnd的成員變量base中,這樣,用戶空間的應用程序就可以直接將要渲染的圖形內容拷貝到這個地址上去,這就相當於是直接將圖形渲染到系統幀緩沖區中去。
在將private_handle_t結構體hnd返回給調用者之前,還需要設置它的成員變量offset,以便可以知道它所描述的圖形緩沖區的起始地址相對於系統幀緩沖區的基地址的偏移量。
至此,在系統幀緩沖區中分配圖形緩沖區的過程就分析完成了,接下來我們再分析在內存在分析圖形緩沖區的過程,即分析函數gralloc_alloc_buffer的實現。
函數gralloc_alloc_buffer也是實現在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
[cpp]
static int gralloc_alloc_buffer(alloc_device_t* dev,
size_t size, int usage, buffer_handle_t* pHandle)
{
int err = 0;
int fd = -1;
size = roundUpToPageSize(size);
fd = ashmem_create_region("gralloc-buffer", size);
if (fd < 0) {
LOGE("couldn't create ashmem (%s)", strerror(-errno));
err = -errno;
}
if (err == 0) {
private_handle_t* hnd = new private_handle_t(fd, size, 0);
gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
dev->common.module);
err = mapBuffer(module, hnd);
if (err == 0) {
*pHandle = hnd;
}
}
LOGE_IF(err, "gralloc failed err=%s", strerror(-err));
return err;
}
這個函數的實現很簡單,它首先調用函數ashmem_create_region來創建一塊匿名共享內存,接著再在這塊匿名共享內存上分配一個圖形緩沖區。注意,這個圖形緩沖區也是使用一個private_handle_t結構體來描述的,不過這個圖形緩沖區的標志值等於0,以區別於在系統幀緩沖區中分配的圖形緩沖區。匿名共享內存的相關知識,可以參考前面Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃一文,以及Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析這篇文章。
從匿名共享內存中分配的圖形緩沖區還需要映射到進程的地址空間來,然後才可以使用,這是通過調用函數mapBuffer來實現的。
函數mapBuffer實現在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
int mapBuffer(gralloc_module_t const* module,
private_handle_t* hnd)
{
void* vaddr;
return gralloc_map(module, hnd, &vaddr);
}
它通過調用另外一個函數gralloc_map來將參數hnd所描述的一個圖形緩沖區映射到當前進程的地址空間來。後面在分析圖形緩沖區的注冊過程時,我們再分析函數gralloc_map的實現。
注意,在Android系統中,在系統幀緩沖區中分配的圖形緩沖區是在SurfaceFlinger服務中使用的,而在內存中分配的圖形緩沖區既可以在SurfaceFlinger服務中使用,也可以在其它的應用程序中使用。當其它的應用程序需要使用圖形緩沖區的時候,它們就會請求SurfaceFlinger服務為它們分配,因此,對於其它的應用程序來說,它們只需要將SurfaceFlinger服務返回來的圖形緩沖區映射到自己的進程地址空間來使用就可以了,這就是後面我們所要分析的圖形緩沖區的注冊過程。
至此,圖形緩沖區的分配過程就分析完成了,接下來我們繼續分析圖形緩沖區的釋放過程。
5. 圖形緩沖區的釋放過程
前面提到,用戶空間的應用程序用到的圖形緩沖區是由Gralloc模塊中的函數gralloc_free來釋放的,這個函數實現在文件hardware/libhardware/modules/gralloc/gralloc.cpp中,如下所示:
[cpp]
static int gralloc_free(alloc_device_t* dev,
buffer_handle_t handle)
{
if (private_handle_t::validate(handle) < 0)
return -EINVAL;
private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(handle);
if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
// free this buffer
private_module_t* m = reinterpret_cast<private_module_t*>(
dev->common.module);
const size_t bufferSize = m->finfo.line_length * m->info.yres;
int index = (hnd->base - m->framebuffer->base) / bufferSize;
m->bufferMask &= ~(1<<index);
} else {
gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(
dev->common.module);
terminateBuffer(module, const_cast<private_handle_t*>(hnd));
}
close(hnd->fd);
delete hnd;
return 0;
}
要釋放的圖形緩沖區使用參數handle來描述。前面提到,從Gralloc模塊中分配的圖形緩沖區是使用private_handle_t結構體來描述的,因此,這裡的參數handle應該指向一個private_handle_t結構體,這是通過調用private_handle_t類的靜態成員函數validate來驗證的。private_handle_t類的靜態成員函數validate的實現可以參考前面第1部分的內容。
要釋放的圖形緩沖區有可能是在系統幀緩沖區分配的,也有可能是在內存中分配的,這可以通過檢查它的標志值flags的PRIV_FLAGS_FRAMEBUFFER位是否等於1來確認。
如果要釋放的圖形緩沖區是在系統幀緩沖區中分配的,那麼首先要知道這個圖形緩沖區是系統幀緩沖區的第index個位置,接著再將變量m所描述的一個private_module_t結構體的成員變量bufferMask的第index位重置為0即可。我們只需要將要釋放的圖形緩沖區的開始地址減去系統幀緩沖區的基地址,再除以一個圖形緩沖區的大小,就可以知道要釋放的圖形緩沖區是系統幀緩沖區的第幾個位置。這個過程剛好是在系統幀緩沖區中分配圖形緩沖區的逆操作。
如果要釋放的圖形緩沖區是內存中分配的,那麼只需要調用另外一個函數terminateBuffer來解除要釋放的圖形緩沖區在當前進程的地址空間中的映射。
最後,這個函數還會將用來描述要釋放的圖形緩沖區的private_handle_t結構體所占用的內存釋放掉,並且將要要釋放的圖形緩沖區所在的系統幀緩沖區或者匿名共享內存的文件描述符關閉掉。
函數terminateBuffer實現在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
int terminateBuffer(gralloc_module_t const* module,
private_handle_t* hnd)
{
if (hnd->base) {
// this buffer was mapped, unmap it now
gralloc_unmap(module, hnd);
}
return 0;
}
它通過調用另外一個函數gralloc_unmap來解除參數hnd所描述的一個圖形緩沖區在當前進程的地址空間中的映射。後面在分析圖形緩沖區的注銷過程時,我們再詳細分析函數gralloc_unmap的實現。
至此,圖形緩沖區的釋放過程就分析完成了,接下來我們繼續分析圖形緩沖區的注冊過程。
6. 圖形緩沖區的注冊過程
前面提到,在Android系統中,所有的圖形緩沖區都是由SurfaceFlinger服務分配的,而當一個圖形緩沖區被分配的時候,它會同時被映射到請求分配的進程的地址空間去,即分配的過程同時也包含了注冊的過程。但是對用戶空間的其它的應用程序來說,它們所需要的圖形緩沖區是在由SurfaceFlinger服務分配的,因此,當它們得到SurfaceFlinger服務分配的圖形緩沖區之後,還需要將這塊圖形緩沖區映射到自己的地址空間來,以便可以使用這塊圖形緩沖區。這個映射的過程即為我們接下來要分析的圖形緩沖區注冊過程。
前面還提到,注冊圖形緩沖區的操作是由Gralloc模塊中的函數gralloc_register_buffer來實現的,這個函數實現在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
int gralloc_register_buffer(gralloc_module_t const* module,
buffer_handle_t handle)
{
if (private_handle_t::validate(handle) < 0)
return -EINVAL;
// if this handle was created in this process, then we keep it as is.
int err = 0;
private_handle_t* hnd = (private_handle_t*)handle;
if (hnd->pid != getpid()) {
void *vaddr;
err = gralloc_map(module, handle, &vaddr);
}
return err;
}
這個函數首先驗證參數handle指向的一塊圖形緩沖區的確是由Gralloc模塊分配的,方法是調用private_handle_t類的靜態成員函數validate來驗證,即如果參數handle指向的是一個private_handle_t結構體,那麼它所指向的一塊圖形緩沖區就是由Gralloc模塊分配的。
通過了上面的檢查之後,函數gralloc_register_buffer還需要檢查當前進程是否就是請求Gralloc模塊分配圖形緩沖區hnd的進程。如果是的話,那麼當前進程在請求Gralloc模塊分配圖形緩沖區hnd的時候,就已經將圖形緩沖區hnd映射進自己的地址空間來了,因此,這時候就不需要重復在當前進程中注冊這個圖形緩沖區。
真正執行注冊圖形緩沖區的操作是由函數gralloc_map來實現的,這個函數也是實現文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
static int gralloc_map(gralloc_module_t const* module,
buffer_handle_t handle,
void** vaddr)
{
private_handle_t* hnd = (private_handle_t*)handle;
if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
size_t size = hnd->size;
void* mappedAddress = mmap(0, size,
PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
if (mappedAddress == MAP_FAILED) {
LOGE("Could not mmap %s", strerror(errno));
return -errno;
}
hnd->base = intptr_t(mappedAddress) + hnd->offset;
//LOGD("gralloc_map() succeeded fd=%d, off=%d, size=%d, vaddr=%p",
// hnd->fd, hnd->offset, hnd->size, mappedAddress);
}
*vaddr = (void*)hnd->base;
return 0;
}
由於在系統幀緩沖區中分配的圖形緩沖區只在SurfaceFlinger服務中使用,而SurfaceFlinger服務在初始化系統幀緩沖區的時候,已經將系統幀緩沖區映射到自己所在的進程中來了,因此,函數gralloc_map如果發現要注冊的圖形緩沖區是在系統幀緩沖區分配的時候,那麼就不需要再執行映射圖形緩沖區的操作了。
如果要注冊的圖形緩沖區是在內存中分配的,即它的標志值flags的PRIV_FLAGS_FRAMEBUFFER位等於1,那麼接下來就需要將它映射到當前進程的地址空間來了。由於要注冊的圖形緩沖區是在文件描述符hnd->fd所描述的一塊匿名共享內存中分配的,因此,我們只需要將文件描述符hnd->fd所描述的一塊匿名共享內存映射到當前進程的地址空間來,就可以將參數hnd所描述的一個圖形緩沖區映射到當前進程的地址空間來。
由於映射文件描述符hnd->fd得到的是一整塊匿名共享內存在當前進程地址空間的基地址,而要注冊的圖形緩沖區可能只占據這塊匿名共享內存的某一小部分,因此,我們還需要將要注冊的圖形緩沖區的在被映射的匿名共享內存中的偏移量hnd->offset加上被映射的匿名共享內存的基地址hnd->base,才可以得到要注冊的圖形緩沖區在當前進程中的訪問地址,這個地址最終又被寫入到hnd->base中去。
注冊圖形緩沖區的過程就是這麼簡單,接下來我們再分析圖形緩沖區的注銷過程。
7. 圖形緩沖區的注銷過程
圖形緩沖區使用完成之後,就需要從當前進程中注銷。前面提到,注銷圖形緩沖區是由Gralloc模塊中的函數gralloc_unregister_buffer來實現的,這個函數實現在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
int gralloc_unregister_buffer(gralloc_module_t const* module,
buffer_handle_t handle)
{
if (private_handle_t::validate(handle) < 0)
return -EINVAL;
// never unmap buffers that were created in this process
private_handle_t* hnd = (private_handle_t*)handle;
if (hnd->pid != getpid()) {
if (hnd->base) {
gralloc_unmap(module, handle);
}
}
return 0;
}
這個函數同樣是首先調用private_handle_t類的靜態成員函數validate來驗證參數handle指向的一塊圖形緩沖區的確是由Gralloc模塊分配的,接著再將將參數handle指向的一塊圖形緩沖區轉換為一個private_handle_t結構體hnd來訪問。
一塊圖形緩沖區只有被注冊過,即被Gralloc模塊中的函數gralloc_register_buffer注冊過,才需要注銷,而由函數gralloc_register_buffer注冊的圖形緩沖區都不是由當前進程分配的,因此,當前進程在注銷一個圖形緩沖區的時候,會檢查要注銷的圖形緩沖區是否是由自己分配的。如果是由自己分配的話,那麼它什麼也不做就返回了。
假設要注銷的圖形緩沖區hnd不是由當前進程分配的,那麼接下來就會調用另外一個函數galloc_unmap來注銷圖形緩沖區hnd。
函數galloc_unmap也是實現在文件hardware/libhardware/modules/gralloc/mapper.cpp中,如下所示:
[cpp]
static int gralloc_unmap(gralloc_module_t const* module,
buffer_handle_t handle)
{
private_handle_t* hnd = (private_handle_t*)handle;
if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
void* base = (void*)hnd->base;
size_t size = hnd->size;
//LOGD("unmapping from %p, size=%d", base, size);
if (munmap(base, size) < 0) {
LOGE("Could not unmap %s", strerror(errno));
}
}
hnd->base = 0;
return 0;
}
這個函數的實現與前面所分析的函數gralloc_map的實現是類似的,只不過它執行的是相反的操作,即將解除一個指定的圖形緩沖區在當前進程的地址空間中的映射,從而完成對這個圖形緩沖區的注銷工作。
這樣,圖形緩沖區的注銷過程就分析完成了,接下來我們再繼續分析一個圖形緩沖區是如何被渲染到系統幀緩沖區去的,即它的內容是如何繪制在設備顯示屏中的。
8. 圖形緩沖區的渲染過程
用戶空間的應用程序將畫面內容寫入到圖形緩沖區中去之後,還需要將圖形緩沖區渲染到系統幀緩沖區中去,這樣才可以把畫面繪制到設備顯示屏中去。前面提到,渲染圖形緩沖區是由Gralloc模塊中的函數gralloc_unregister_buffer來實現的,這個函數實現在文件hardware/libhardware/modules/gralloc/framebuffer.cpp中,如下所示:
[cpp]
static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)
{
if (private_handle_t::validate(buffer) < 0)
return -EINVAL;
fb_context_t* ctx = (fb_context_t*)dev;
private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);
private_module_t* m = reinterpret_cast<private_module_t*>(
dev->common.module);
if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
const size_t offset = hnd->base - m->framebuffer->base;
m->info.activate = FB_ACTIVATE_VBL;
m->info.yoffset = offset / m->finfo.line_length;
if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {
LOGE("FBIOPUT_VSCREENINFO failed");
m->base.unlock(&m->base, buffer);
return -errno;
}
m->currentBuffer = buffer;
} else {
// If we can't do the page_flip, just copy the buffer to the front
// FIXME: use copybit HAL instead of memcpy
void* fb_vaddr;
void* buffer_vaddr;
m->base.lock(&m->base, m->framebuffer,
GRALLOC_USAGE_SW_WRITE_RARELY,
0, 0, m->info.xres, m->info.yres,
&fb_vaddr);
m->base.lock(&m->base, buffer,
GRALLOC_USAGE_SW_READ_RARELY,
0, 0, m->info.xres, m->info.yres,
&buffer_vaddr);
memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);
m->base.unlock(&m->base, buffer);
m->base.unlock(&m->base, m->framebuffer);
}
return 0;
}
參數buffer用來描述要渲染的圖形緩沖區,它指向的必須要是一個private_handle_t結構體,這是通過調用private_handle_t類的靜態成員函數validate來驗證的。驗證通過之後,就可以將參數buffer所描述的一個buffer_handle_t結構體轉換成一個private_handle_t結構體hnd。
參數dev用來描述在Gralloc模塊中的一個fb設備。從前面第3部分的內容可以知道,在打開fb設備的時候,Gralloc模塊返回給調用者的實際上是一個fb_context_t結構體,因此,這裡就可以將參數dev所描述的一個framebuffer_device_t結構體轉換成一個fb_context_t結構體ctx。
參數dev的成員變量common指向了一個hw_device_t結構體,這個結構體的成員變量module指向了一個Gralloc模塊。從前面第1部分的內容可以知道,一個Gralloc模塊是使用一個private_module_t結構體來描述的,因此,我們可以將dev->common.moudle轉換成一個private_module_t結構體m。
由於private_handle_t結構體hnd所描述的圖形緩沖區可能是在系統幀緩沖區分配的,也有可能是內存中分配的,因此,我們分兩種情況來討論圖形緩沖區渲染過程。
當private_handle_t結構體hnd所描述的圖形緩沖區是在系統幀緩沖區中分配的時候,即這個圖形緩沖區的標志值flags的PRIV_FLAGS_FRAMEBUFFER位等於1的時候,我們是不需要將圖形緩沖區的內容拷貝到系統幀緩沖區去的,因為我們將內容寫入到圖形緩沖區的時候,已經相當於是將內容寫入到了系統幀緩沖區中去了。雖然在這種情況下,我們不需要將圖形緩沖區的內容拷貝到系統幀緩沖區去,但是我們需要告訴系統幀緩沖區設備將要渲染的圖形緩沖區作為系統當前的輸出圖形緩沖區,這樣才可以將要渲染的圖形緩沖區的內容繪制到設備顯示屏來。例如,假設系統幀緩沖區有2個圖形緩沖區,當前是以第1個圖形緩沖區作為輸出圖形緩沖區的,這時候如果我們需要渲染第2個圖形緩沖區,那麼就必須告訴系統幀繪沖區設備,將第2個圖形緩沖區作為輸出圖形緩沖區。
設置系統幀緩沖區的當前輸出圖形緩沖區是通過IO控制命令FBIOPUT_VSCREENINFO來進行的。IO控制命令FBIOPUT_VSCREENINFO需要一個fb_var_screeninfo結構體作為參數。從前面第3部分的內容可以知道,private_module_t結構體m的成員變量info正好保存在我們所需要的這個fb_var_screeninfo結構體。有了個m->info這個fb_var_screeninfo結構體之後,我們只需要設置好它的成員變量yoffset的值(不用設置成員變量xoffset的值是因為所有的圖形緩沖區的寬度是相等的),就可以將要渲染的圖形緩沖區設置為系統幀緩沖區的當前輸出圖形緩沖區。fb_var_screeninfo結構體的成員變量yoffset保存的是當前輸出圖形緩沖區在整個系統幀緩沖區的縱向偏移量,即Y偏移量。我們只需要將要渲染的圖形緩沖區的開始地址hnd->base的值減去系統幀緩沖區的基地址m->framebuffer->base的值,再除以圖形緩沖區一行所占據的字節數m->finfo.line_length,就可以得到所需要的Y偏移量。
在執行IO控制命令FBIOPUT_VSCREENINFO之前,還會將作為參數的fb_var_screeninfo結構體的成員變量activate的值設置FB_ACTIVATE_VBL,表示要等到下一個垂直同步事件出現時,再將當前要渲染的圖形緩沖區的內容繪制出來。這樣做的目的是避免出現屏幕閃爍,即避免前後兩個圖形緩沖區的內容各有一部分同時出現屏幕中。
成功地執行完成IO控制命令FBIOPUT_VSCREENINFO之後,函數還會將當前被渲染的圖形緩沖區保存在private_module_t結構體m的成員變量currentBuffer中,以便可以記錄當前被渲染的圖形緩沖區是哪一個。
當private_handle_t結構體hnd所描述的圖形緩沖區是在內存中分配的時候,即這個圖形緩沖區的標志值flags的PRIV_FLAGS_FRAMEBUFFER位等於0的時候,我們就需要將它的內容拷貝到系統幀緩沖區中去了。這個拷貝的工作是通過調用函數memcpy來完成的。在拷貝之前,我們需要三個參數。第一個參數是要渲染的圖形緩沖區的起址地址,這個地址保存在參數buffer所指向的一個private_handle_t結構體中。第二個參數是要系統幀緩沖區的基地址,這個地址保存在private_module_t結構體m的成員變量framebuffer所指向的一個private_handle_t結構體中。第三個參數是要拷貝的內容的大小,這個大小就剛好是一個屏幕像素所占據的內存的大小。屏幕高度由m->info.yres來描述,而一行屏幕像素所占用的字節數由m->finfo.line_length來描述,將這兩者相乘,就可以得到一個屏幕像素所占據的內存的大小。
在將一塊內存緩沖區的內容拷貝到系統幀緩沖區中去之前,需要對這兩塊緩沖區進行鎖定,以保證在拷貝的過程中,這兩塊緩沖區的內容不會被修改。這個鎖定的工作是由Gralloc模塊中的函數gralloc_lock來實現的。從前面第1部分的內容可以知道,Gralloc模塊中的函數gralloc_lock的地址正好就保存在private_module_t結構體m的成員變量base所描述的一個gralloc_module_t結構體的成員函數lock中。
在調用函數gralloc_lock來鎖定一塊緩沖區之後,還可以通過最後一個輸出參數來獲得被鎖定的緩沖區的開始地址,因此,通過調用函數gralloc_lock來鎖定要渲染的圖形緩沖區以及系統幀緩沖區,就可以得到前面所需要的第一個和第二個參數。
將要渲染的圖形緩沖區的內容拷貝到系統幀緩沖區之後,就可以解除前面對它們的鎖定了,這個解鎖的工作是由Gralloc模塊中的函數gralloc_unlock來實現的。從前面第1部分的內容可以知道,Gralloc模塊中的函數gralloc_unlock的地址正好就保存在private_module_t結構體m的成員變量base所描述的一個gralloc_module_t結構體的成員函數unlock中。
這樣,一個圖形緩沖區的渲染過程就分析完成了。
為了完整性起見,最後我們再簡要分析函數gralloc_lock和gralloc_unlock的實現,以便可以了解一個圖形緩沖區的鎖定和解鎖操作是如何實現的。
函數gralloc_lock實現在文件hardware/libhardware/modules/gralloc/mapper.cpp文件中,如下所示:
[cpp]
int gralloc_lock(gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr)
{
// this is called when a buffer is being locked for software
// access. in thin implementation we have nothing to do since
// not synchronization with the h/w is needed.
// typically this is used to wait for the h/w to finish with
// this buffer if relevant. the data cache may need to be
// flushed or invalidated depending on the usage bits and the
// hardware.
if (private_handle_t::validate(handle) < 0)
return -EINVAL;
private_handle_t* hnd = (private_handle_t*)handle;
*vaddr = (void*)hnd->base;
return 0;
}
從這裡可以看出,函數gralloc_lock其實並沒有執行鎖定參數handle所描述的一個緩沖區的操作,它只簡單地將要鎖定的緩沖區的開始地址返回給調用者。
理論上來說,函數gralloc_lock應該檢查參數handle所描述的一個緩沖區是否正在被其進程或者線程使用。如果是的話,那麼函數gralloc_lock就必須要等待,直到要鎖定的緩沖區被其它進程或者線程使用結束為止,以便接下來可以獨占它。由於函數gralloc_lock實際上並沒有作這些操作,因此,就必須要由調用者來保證要鎖定的緩沖區當前是沒有被其它進程或者線程使用的。
函數gralloc_unlock也是實現在文件hardware/libhardware/modules/gralloc/mapper.cpp文件中,如下所示:
[cpp]
int gralloc_unlock(gralloc_module_t const* module,
buffer_handle_t handle)
{
// we're done with a software buffer. nothing to do in this
// implementation. typically this is used to flush the data cache.
if (private_handle_t::validate(handle) < 0)
return -EINVAL;
return 0;
}
函數gralloc_unlock執行的操作本來是剛好與函數gralloc_lock相反的,但是由於函數gralloc_lock並沒有真實地鎖定參數handle所描述的一個緩沖區的,因此,函數gralloc_unlock是不需要執行實際的解鎖工作的。
至此,我們就分析完成Android幀緩沖區硬件抽象層模塊Gralloc的實現原理了。從分析的過程可以知道,為了在屏幕中繪制一個指定的畫面,我們需要:
1. 分配一個匹配屏幕大小的圖形緩沖區
2. 將分配好的圖形緩沖區注冊(映射)到當前進程的地址空間來
3. 將要繪制的畫面的內容寫入到已經注冊好的圖形緩沖區中去,並且渲染(拷貝)到系統幀緩沖區中去
為了實現以上三個操作,我們還需要:
1. 加載Gralloc模塊
2. 打開Gralloc模塊中的gralloc設備和fb設備
其中,gralloc設備負責分配圖形緩沖區,Gralloc模塊負責注冊圖形緩沖區,而fb設備負責渲染圖形緩沖區。
理解了Gralloc模塊的實現原理之後,就可以為後續分析SurfaceFlinger服務的實現打下堅實的基礎了。
作者:Luoshengyang
在上一篇文章中介紹了使用非RxJava環境下,使用Handler機制SyncBarrier的特性實現預加載功能的方法。在RxJava的環境下使用BehaviorSubje
本文接著實現“確認密碼”功能,也即是用戶以前設置過密碼,現在只需要輸入確認密碼布局文件和《Android 手機衛士--設置密碼對話框》中的布局基本類似,所有copy一下,
Fragment碎片(Fragment)是一種可以嵌入在活動當中的UI片段,它能讓程序更加合理和充分地利用大屏幕的空間,因而在平板上應用的非常廣泛。碎片是什麼這是《第一行
Working with System PermissionsTo protect the system’s integrity and the user&r