來自: Android技術(shù)特工隊
作者: Aaron
主頁: http://www.wxtlife.com/
原文連接:http://www.wxtlife.com/2017/06/07/Android-framebuffer/
如果想加入Android技術(shù)交流群俱笛,請長按識別二維碼關(guān)注下方公眾號肖爵,點擊“加群”獲取加群方式。
FrameBuffer 介紹
FrameBuffer中文譯名為幀緩沖驅(qū)動绍移,它是出現(xiàn)在2.2.xx內(nèi)核中的一種驅(qū)動程序接口鹊碍。主設(shè)備號為29呵曹,次設(shè)備號遞增。
Linux抽象出FrameBuffer這個設(shè)備來供用戶態(tài)進程實現(xiàn)直接寫屏衬衬。FrameBuffer機制模仿顯卡的功能殖妇,將顯卡硬件結(jié)構(gòu)抽象掉刁笙,可以通過FrameBuffer的讀寫直接對顯存進行操作。用戶可以將FrameBuffer看成是顯示內(nèi)存的一個映像谦趣,將其映射到進程地址空間之后疲吸,就可以直接進行讀寫操作,而寫操作可以立即反應(yīng)在屏幕上前鹅。
這種操作是抽象的磅氨,統(tǒng)一的。用戶不必關(guān)心物理顯存的位置嫡纠、換頁機制等等具體細(xì)節(jié),這些都是由FrameBuffer設(shè)備驅(qū)動來完成的延赌。
FrameBuffer實際上就是嵌入式系統(tǒng)中專門為GPU所保留的一塊連續(xù)的物理內(nèi)存除盏,LCD通過專門的總線從framebuffer讀取數(shù)據(jù),顯示到屏幕上挫以。
FrameBuffer本質(zhì)上是一塊顯示緩存者蠕,往顯示緩存中寫入特定格式的數(shù)據(jù)就意味著向屏幕輸出內(nèi)容。所以說FrameBuffer就是一塊白板掐松。
屏幕位置從上到下踱侣,從左至右與內(nèi)存地址是順序的線性關(guān)系
FrameBuffer 使用
framebuffer的設(shè)備文件在Linux下一般是 /dev/fb0
、/dev/fb1
等大磺,但在Android下面一般為/dev/graphics/fb0
,/dev/graphics/fb1
等
注意: 系統(tǒng)中至少要存在一個顯示屏抡句,因此,名稱為“fb0”的設(shè)備是肯定會存在的杠愧,否則的話待榔,就是出錯了。
操作framebuffer的主要步驟
1流济、打開可用的FrameBuffer設(shè)備锐锣;
fbfd = open("/dev/graphics/fb0", O_RDWR);
O_RDWR
是已可讀寫的方式打開文件
2、計算映射大小
用ioctrl操作取得當(dāng)前顯示屏幕的參數(shù)绳瘟,如屏幕分辨率雕憔,每個像素點的比特數(shù)。根據(jù)屏幕參數(shù)可計算屏幕緩沖區(qū)的大小糖声。
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo); // fb_fix_screeninfo 通過fbfd獲取屏幕固定的相關(guān)信息
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); // fb_var_screeninfo 通過fbfd獲取可變的信息斤彼,可以調(diào)用參數(shù)為`FBIOPUT_VSCREENINFO`的重新進行設(shè)置
從fb_var_screeninfo中可以獲取xoffset ,yoffset的偏移量分瘦,以及屏幕可見行列像素點(xres,yres),以及一個像素所占用的位數(shù)bits_per_pixel畅卓。
從fb_fix_screeninfo 中可以獲取到framebuffer的內(nèi)存空間大小finfo.smem_len擅腰,每行占用的字節(jié)數(shù)line_length等。
這些信息都是對我們下一步來計算需要映射多大的內(nèi)存空間有很大的幫助翁潘,size 可以直接等于 finfo.smem_len, 或者 xres * yres * bits_per_pixel >> 3
3趁冈、通過mmap映射地址空間
通過mmap函數(shù)把顯卡的物理內(nèi)存空間映射到用戶空間地址上
char *fbp = (char *)mmap(start, size, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, offsize);
各個參數(shù)的含義如下:
- start 指向欲映射的內(nèi)存起始地址,通常設(shè)為 NULL拜马,代表讓系統(tǒng)自動選定地址渗勘,映射成功后返回該地址。
- size 代表將文件中多大的部分映射到內(nèi)存俩莽。
- PROT_READ | PROT_WRITE 為可讀可寫模式
- MAP_SHARED 對映射區(qū)域的寫入數(shù)據(jù)會復(fù)制回文件內(nèi)旺坠,而且允許其他映射該文件的進程共享。
- fbfd 要映射到內(nèi)存中的文件描述符,也就是open文件后的描述符扮超。
- offsize 文件映射的偏移量
關(guān)于open取刃、mmap的相關(guān)信息可以參考博客:http://www.wxtlife.com/2016/01/17/Android-memory-map/
4、更改內(nèi)存空間里的像素數(shù)據(jù)并顯示出刷;
fbp則是映射framebuffer后的內(nèi)存首地址璧疗,整個framebuffer的地址是線性的,與整個屏幕大小從左到右馁龟,從上到下映射的崩侠。所以操作framebuffer是按照每個像素去操作的,每個像素都需要計算他的偏移量也就是每個像素的內(nèi)存地址空間.
例如在(x坷檩,y)位置寫入顏色 pixel值却音。
4.1 首先先計算偏移量
offset = (x + y * screen_width) * 4; // (4個字節(jié))
上面未考慮多buffer切換的地址空間的情況
4.2 給偏移量的內(nèi)存地址賦值
*((uint32_t *)(fbp + offset)) = pixel;
5、退出時關(guān)閉framebuffer設(shè)備矢炼。
munmap(fbp, size);
close(fbfd);
size需要與mmap時一樣系瓢,會導(dǎo)致內(nèi)存泄露問題。
查看一個完整的示例demo :
int main() {
int fbfd = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
long int screensize = 0;
char *fbp = 0;
long int location = 0;
// Open the file for reading and writing
fbfd = open("/dev/graphics/fb0", O_RDWR);
if (!fbfd) {
printf("Error: cannot open framebuffer device.\n");
exit(1);
}
printf("The framebuffer device was opened successfully.\n");
// Get fixed screen information
if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
printf("Error reading fixed information.\n");
exit(2);
}
// Get variable screen information
if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
printf("Error reading variable information.\n");
exit(3);
}
screensize = finfo.smem_len;
// screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel >> 3 // >>3 表示算出字節(jié)數(shù)
fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, 0);
if ((int)fbp == -1) {
printf("Error: failed to map framebuffer device to memory.\n");
exit(4);
}
FrameBuffer 相關(guān)結(jié)構(gòu)
FrameBuffer設(shè)備驅(qū)動基于如下兩個文件:
- linux/include/linux/fb.h
- linux/drivers/video/fbmem.c
FrameBuffer 主要包含的結(jié)構(gòu)有以下:fb_info 句灌,fb_ops 八拱,fb_var_screeninfo,fb_fix_screeninfo,上面的結(jié)構(gòu)都定義在fb.h里涯塔。
fb_var_screeninfo
用于記錄用戶可修改的顯示屬性參數(shù)肌稻,包括屏幕分辨率、每個像素點的比特數(shù)等匕荸。
顯卡的顯示屬性,用戶可修改爹谭,此數(shù)據(jù)結(jié)構(gòu)中,定義了偏移量(xoffset ,yoffset)榛搔、可見行列像素點(xres诺凡,yres)东揣、每個像素所占bit位數(shù)(bits_per_pixel), 虛擬分辨率(xres_virtual、yres_virtual)在顯存中包含的分辨率等信息腹泌,這些都是我們常見也是常用的到的嘶卧。
這些數(shù)據(jù)我們是可以通過ioctl函數(shù)來獲取,獲取操作如下:ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)
凉袱。也可以重新進行賦值芥吟,之后再將設(shè)置進系統(tǒng):設(shè)置如下:ioctl(fbfd, FBIOPUT_VSCREENINFO, &finfo)
數(shù)據(jù)結(jié)構(gòu)如下:
struct fb_var_screeninfo {
__u32 xres; /* 行可見像素*/
__u32 yres; /* 列可見像素*/
__u32 xres_virtual; /* 行虛擬像素*/
__u32 yres_virtual; /* 列虛擬像素*/
__u32 xoffset; /* 水平偏移量*/
__u32 yoffset; /* 垂直偏移量*/
__u32 bits_per_pixel;/*每個像素所占bit位數(shù)*/
__u32 grayscale; /* 灰色刻度*/
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* 圖像高度*/
__u32 width; /* 圖像寬度*/
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
};
fb_fix_screeninfo
這個結(jié)構(gòu)在顯卡被設(shè)定模式后創(chuàng)建,它描述顯示卡的屬性专甩,并且系統(tǒng)運行時不能被修改钟鸵;比如FrameBuffer內(nèi)存的起始地址。它依賴于被設(shè)定的模式涤躲,當(dāng)一個模式被設(shè)定后棺耍,內(nèi)存信息由顯示卡硬件給出,內(nèi)存的位置等信息就不可以修改种樱。
顯卡的硬件屬性, 用戶不可修改, 驅(qū)動程序初始化時設(shè)置蒙袍。比如可以獲取內(nèi)存空間大小,起始地址嫩挤,每行占用的字節(jié)數(shù)line_length等等害幅。
數(shù)據(jù)結(jié)構(gòu)如下:
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start;/* Start of frame buffer mem */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start;/* Start of Memory Mapped I/O */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
__u16 reserved[3]; /* Reserved for future compatibility */
};
fb_ops
是提供給底層設(shè)備驅(qū)動的一個接口,用戶應(yīng)用可以使用 ioctl() 系統(tǒng)調(diào)用來操作設(shè)備俐镐。
fb_cmap
描述設(shè)備無關(guān)的顏色映射信息〔负撸可以通過 FBIOGETCMAP 和 FBIOPUTCMAP 對應(yīng)的 ioctl 操作設(shè)定或獲取顏色映射信息佩抹。主要是顏色映射表,顏色相關(guān)一些映射信息取董。
fb_info
結(jié)構(gòu)僅在內(nèi)核中可見棍苹,在這個結(jié)構(gòu)中有一個fb_ops指針,指向驅(qū)動設(shè)備工作所需的函數(shù)集茵汰,是Linux為幀緩沖設(shè)備定義的驅(qū)動層接口枢里。它不僅包含了底層函數(shù),而且還有記錄設(shè)備狀態(tài)的數(shù)據(jù)蹂午。每個幀緩沖設(shè)備都與一個fb_info結(jié)構(gòu)相對應(yīng)栏豺。
結(jié)構(gòu)如下:
ioctl中request參數(shù):
- FBIOGET_VSCREENINFO 表示用戶獲取屏幕的可變參數(shù);
- FBIOPUT_VSCREENINFO 表示用戶設(shè)置可變的屏幕參數(shù)豆胸;
- FBIOGET_FSCREENINFO 表示用戶獲得屏幕的固定參數(shù)奥洼;
- FBIOBLANK表示調(diào)用sep4020fb_blank函數(shù)清空液晶屏;
- FBIOPUTCMAP 表示設(shè)置屏幕的顏色表晚胡;
- FBIOGETCMAP 表示獲得顏色表灵奖。
雙緩沖機制
Android 使用SurfaceFlinger作為屏幕合成引擎嚼沿。它管理來自各個窗口的Surface objects,然后將其寫入到framebuffer去瓷患。SurfaceFlinger使用前buffer來合成骡尽,后buffer來繪制。一旦繪制完成擅编,Android通過頁翻轉(zhuǎn)操作攀细,交換Y軸坐標(biāo)的偏移量,選擇不同buffer沙咏。在EGL顯示服務(wù)初始化時辨图,如果虛擬Y軸分辨率大于實際Y軸分辨率,說明framebuffer可以直接使用雙緩沖肢藐。否則故河,后buffer要復(fù)制到前buffer,這樣會導(dǎo)致頁交換延遲吆豹。為了提高系統(tǒng)性能鱼的,F(xiàn)ramebuffer驅(qū)動最好提供雙緩沖機制。
雙緩沖機制的原理
所有畫圖操作將它們畫圖的結(jié)果保存在一塊系統(tǒng)內(nèi)存區(qū)域中痘煤,這塊區(qū)域通常被稱作“后緩沖區(qū)(backbuffer)”凑阶,當(dāng)所有的繪圖操作結(jié)束之后,系統(tǒng)通過換頁機制將繪制區(qū)域指向先前的后緩沖區(qū)衷快,然后進行繪制顯示宙橱,而原來的繪制緩沖區(qū)就變?yōu)椤昂缶彌_區(qū)”,之后按照這種情況不停循環(huán)切換蘸拔。這個復(fù)制操作通常要跟顯示器的光棧束同步师郑,以避免撕裂。雙緩沖機制必須要求有比單緩沖更多的顯示內(nèi)存和CPU消耗時間调窍,因為“后緩沖區(qū)”需要顯示內(nèi)存宝冕,而復(fù)制操作和等待同步需要CPU時間。


雙緩沖是一種畫圖技術(shù)邓萨,使用這種技術(shù)可以使得畫圖沒有(至少是減少)閃爍地梨、撕裂等不良效果,并減少等待時間缔恳。
緩沖區(qū)切換步驟:
- 把fb驅(qū)動的framebuffer通過mmap映射到應(yīng)用空間的內(nèi)存地址map_base宝剖,一般來說framebuffer size是2*framesize或者3*framesize 大小(和平臺相關(guān))
- 把第一幀數(shù)據(jù)寫入map_base
- 調(diào)用FBIOPAN_DISPLAY顯示
- 把第二幀數(shù)據(jù)寫入map_base+framesize處
- 調(diào)用FBIOPAN_DISPLAY
- 重復(fù)step2~step5
FBIOPAN_DISPLAY 在linux的注釋里是“平移顯示”的意思,調(diào)用FBIOPAN_DISPLAY時歉甚,會傳一個y坐標(biāo)偏移量yoffset給驅(qū)動诈闺,然后驅(qū)動會把當(dāng)前顯存的指針偏移 “yoffset X 屏幕寬度 X 位色字節(jié)數(shù)” 個字節(jié),這樣就好像實現(xiàn)了圖像的y坐標(biāo)平移铃芦,也就是“平移顯示”雅镊。當(dāng)這個yoffset等于屏幕高度的時候襟雷,就實現(xiàn)了顯存的切換。
參考鏈接
http://www.cnblogs.com/armlinux/archive/2012/02/25/2396760.html
http://blog.csdn.net/yangwen123/article/details/12096483
如果想加入Android技術(shù)交流群仁烹,請長按識別二維碼關(guān)注下方公眾號耸弄,點擊“加群”獲取加群方式。