来自: Android技术特工队
作者: Aaron
主页: http://www.wxtlife.com/
原文连接:http://www.wxtlife.com/2017/06/07/Android-framebuffer/
如果想加入Android技术交流群,请长按识别二维码关注下方公众号,点击“加群”获取加群方式。
FrameBuffer 介绍
FrameBuffer中文译名为帧缓冲驱动,它是出现在2.2.xx内核中的一种驱动程序接口。主设备号为29,次设备号递增。
Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。FrameBuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过FrameBuffer的读写直接对显存进行操作。用户可以将FrameBuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。
这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由FrameBuffer设备驱动来完成的。
FrameBuffer实际上就是嵌入式系统中专门为GPU所保留的一块连续的物理内存,LCD通过专门的总线从framebuffer读取数据,显示到屏幕上。
FrameBuffer本质上是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。
屏幕位置从上到下,从左至右与内存地址是顺序的线性关系
FrameBuffer 使用
framebuffer的设备文件在Linux下一般是 /dev/fb0
、/dev/fb1
等,但在Android下面一般为/dev/graphics/fb0
,/dev/graphics/fb1
等
注意: 系统中至少要存在一个显示屏,因此,名称为“fb0”的设备是肯定会存在的,否则的话,就是出错了。
操作framebuffer的主要步骤
1、打开可用的FrameBuffer设备;
fbfd = open("/dev/graphics/fb0", O_RDWR);
O_RDWR
是已可读写的方式打开文件
2、计算映射大小
用ioctrl操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo); // fb_fix_screeninfo 通过fbfd获取屏幕固定的相关信息
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); // fb_var_screeninfo 通过fbfd获取可变的信息,可以调用参数为`FBIOPUT_VSCREENINFO`的重新进行设置
从fb_var_screeninfo中可以获取xoffset ,yoffset的偏移量,以及屏幕可见行列像素点(xres,yres),以及一个像素所占用的位数bits_per_pixel。
从fb_fix_screeninfo 中可以获取到framebuffer的内存空间大小finfo.smem_len,每行占用的字节数line_length等。
这些信息都是对我们下一步来计算需要映射多大的内存空间有很大的帮助,size 可以直接等于 finfo.smem_len, 或者 xres * yres * bits_per_pixel >> 3
3、通过mmap映射地址空间
通过mmap函数把显卡的物理内存空间映射到用户空间地址上
char *fbp = (char *)mmap(start, size, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, offsize);
各个参数的含义如下:
- start 指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
- size 代表将文件中多大的部分映射到内存。
- PROT_READ | PROT_WRITE 为可读可写模式
- MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
- fbfd 要映射到内存中的文件描述符,也就是open文件后的描述符。
- offsize 文件映射的偏移量
关于open、mmap的相关信息可以参考博客:http://www.wxtlife.com/2016/01/17/Android-memory-map/
4、更改内存空间里的像素数据并显示;
fbp则是映射framebuffer后的内存首地址,整个framebuffer的地址是线性的,与整个屏幕大小从左到右,从上到下映射的。所以操作framebuffer是按照每个像素去操作的,每个像素都需要计算他的偏移量也就是每个像素的内存地址空间.
例如在(x,y)位置写入颜色 pixel值。
4.1 首先先计算偏移量
offset = (x + y * screen_width) * 4; // (4个字节)
上面未考虑多buffer切换的地址空间的情况
4.2 给偏移量的内存地址赋值
*((uint32_t *)(fbp + offset)) = pixel;
5、退出时关闭framebuffer设备。
munmap(fbp, size);
close(fbfd);
size需要与mmap时一样,会导致内存泄露问题。
查看一个完整的示例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 表示算出字节数
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 相关结构
FrameBuffer设备驱动基于如下两个文件:
- linux/include/linux/fb.h
- linux/drivers/video/fbmem.c
FrameBuffer 主要包含的结构有以下:fb_info ,fb_ops ,fb_var_screeninfo,fb_fix_screeninfo,上面的结构都定义在fb.h里。
fb_var_screeninfo
用于记录用户可修改的显示属性参数,包括屏幕分辨率、每个像素点的比特数等。
显卡的显示属性,用户可修改,此数据结构中,定义了偏移量(xoffset ,yoffset)、可见行列像素点(xres,yres)、每个像素所占bit位数(bits_per_pixel), 虚拟分辨率(xres_virtual、yres_virtual)在显存中包含的分辨率等信息,这些都是我们常见也是常用的到的。
这些数据我们是可以通过ioctl函数来获取,获取操作如下:ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)
。也可以重新进行赋值,之后再将设置进系统:设置如下:ioctl(fbfd, FBIOPUT_VSCREENINFO, &finfo)
数据结构如下:
struct fb_var_screeninfo {
__u32 xres; /* 行可见像素*/
__u32 yres; /* 列可见像素*/
__u32 xres_virtual; /* 行虚拟像素*/
__u32 yres_virtual; /* 列虚拟像素*/
__u32 xoffset; /* 水平偏移量*/
__u32 yoffset; /* 垂直偏移量*/
__u32 bits_per_pixel;/*每个像素所占bit位数*/
__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
这个结构在显卡被设定模式后创建,它描述显示卡的属性,并且系统运行时不能被修改;比如FrameBuffer内存的起始地址。它依赖于被设定的模式,当一个模式被设定后,内存信息由显示卡硬件给出,内存的位置等信息就不可以修改。
显卡的硬件属性, 用户不可修改, 驱动程序初始化时设置。比如可以获取内存空间大小,起始地址,每行占用的字节数line_length等等。
数据结构如下:
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
是提供给底层设备驱动的一个接口,用户应用可以使用 ioctl() 系统调用来操作设备。
fb_cmap
描述设备无关的颜色映射信息。可以通过 FBIOGETCMAP 和 FBIOPUTCMAP 对应的 ioctl 操作设定或获取颜色映射信息。主要是颜色映射表,颜色相关一些映射信息。
fb_info
结构仅在内核中可见,在这个结构中有一个fb_ops指针,指向驱动设备工作所需的函数集,是Linux为帧缓冲设备定义的驱动层接口。它不仅包含了底层函数,而且还有记录设备状态的数据。每个帧缓冲设备都与一个fb_info结构相对应。
结构如下:
ioctl中request参数:
- FBIOGET_VSCREENINFO 表示用户获取屏幕的可变参数;
- FBIOPUT_VSCREENINFO 表示用户设置可变的屏幕参数;
- FBIOGET_FSCREENINFO 表示用户获得屏幕的固定参数;
- FBIOBLANK表示调用sep4020fb_blank函数清空液晶屏;
- FBIOPUTCMAP 表示设置屏幕的颜色表;
- FBIOGETCMAP 表示获得颜色表。
双缓冲机制
Android 使用SurfaceFlinger作为屏幕合成引擎。它管理来自各个窗口的Surface objects,然后将其写入到framebuffer去。SurfaceFlinger使用前buffer来合成,后buffer来绘制。一旦绘制完成,Android通过页翻转操作,交换Y轴坐标的偏移量,选择不同buffer。在EGL显示服务初始化时,如果虚拟Y轴分辨率大于实际Y轴分辨率,说明framebuffer可以直接使用双缓冲。否则,后buffer要复制到前buffer,这样会导致页交换延迟。为了提高系统性能,Framebuffer驱动最好提供双缓冲机制。
双缓冲机制的原理
所有画图操作将它们画图的结果保存在一块系统内存区域中,这块区域通常被称作“后缓冲区(backbuffer)”,当所有的绘图操作结束之后,系统通过换页机制将绘制区域指向先前的后缓冲区,然后进行绘制显示,而原来的绘制缓冲区就变为“后缓冲区”,之后按照这种情况不停循环切换。这个复制操作通常要跟显示器的光栈束同步,以避免撕裂。双缓冲机制必须要求有比单缓冲更多的显示内存和CPU消耗时间,因为“后缓冲区”需要显示内存,而复制操作和等待同步需要CPU时间。
双缓冲是一种画图技术,使用这种技术可以使得画图没有(至少是减少)闪烁、撕裂等不良效果,并减少等待时间。
缓冲区切换步骤:
- 把fb驱动的framebuffer通过mmap映射到应用空间的内存地址map_base,一般来说framebuffer size是2*framesize或者3*framesize 大小(和平台相关)
- 把第一帧数据写入map_base
- 调用FBIOPAN_DISPLAY显示
- 把第二帧数据写入map_base+framesize处
- 调用FBIOPAN_DISPLAY
- 重复step2~step5
FBIOPAN_DISPLAY 在linux的注释里是“平移显示”的意思,调用FBIOPAN_DISPLAY时,会传一个y坐标偏移量yoffset给驱动,然后驱动会把当前显存的指针偏移 “yoffset X 屏幕宽度 X 位色字节数” 个字节,这样就好像实现了图像的y坐标平移,也就是“平移显示”。当这个yoffset等于屏幕高度的时候,就实现了显存的切换。
参考链接
http://www.cnblogs.com/armlinux/archive/2012/02/25/2396760.html
http://blog.csdn.net/yangwen123/article/details/12096483
如果想加入Android技术交流群,请长按识别二维码关注下方公众号,点击“加群”获取加群方式。