FrameBuffer是linux提供的显存驱动,在android环境设备节点是/dev/graphics/fb*(支持多个屏幕显示,第一个fb0是主显示屏幕,在linux下一般是/dev/fb*)。FrameBuffer的目的就是通过对硬件的封装抽象,让上层通过设备节点文件的方式,操作硬件去显示某个内存的内容。
文章目录
- 一、framebuffer模块定义与规格场景分析
- 模块定义:fb的本质就是把 指定内存 的 内容显示到屏上
- framebuffer常见的设计规格
- 二、framebuffer主要流程与原理分析
- 1、打开/dev/fb*设备 获取设备访问句柄
- 2、设置显示相关属性fb_var_screeninfo/fb_fix_screeninfon:
- 3、映射显存,并始绘制内容
- 4、通知驱动刷新显示位置(linux标准PAN_DISPLAY方式)
- 三、framebuffer驱动关键设计
- 1、硬件设计抽象 与 版本差异化处理
- 2、中断业务设计与优化
- 3、Android框架引入的fence 私有接口和Vsync
一、framebuffer模块定义与规格场景分析
我们先明确一点,fb是一个和硬件逻辑强耦合的硬件驱动模块,它就是图形层的逻辑抽象驱动。
模块定义:fb的本质就是把 指定内存 的 内容显示到屏上
例如下图,两块显存,显存1是一个需要全屏显示的图片,它的内存格式RGB565 分辨率720p 走 fb0驱动,显存2是一个鼠标,格式是ARGB8888 分辨率64x64 走fb1驱动,显示设备是一个FHD分辨率的RGB888设备。
这个过程一个典型的逻辑处理距离举例:fb0对应的逻辑图层 读取显存1的720p的RGB565数据 缩放到 FHD然后做格式转换 叠加到设备 显示通路上,fb1的逻辑图层读取 显存2的64x64的数据,转换到显示设备 叠加到对应的坐标上(层之间存在Zorder叠加顺序,鼠标层在上)。
framebuffer常见的设计规格
fb常见的典型规格:支持几个图形层(如果只有一个图层,那么鼠标必须叠加到内容中),每个图形层 支持哪些 像素数据格式 / 分辨率 / 颜色空间 / 是否支持裁剪crop / 是否支持显示偏移设置 / 是否支持压缩数据解压读取(降低带宽) / 是否支持缩放 等;
二、framebuffer主要流程与原理分析
一个典型的fb访问流程如下:
1、打开/dev/fb*设备 获取设备访问句柄
linux遵循一切皆文件的思想,是通过文件的方式访问硬件驱动,图形fb设备也是如此,应用需要先open设备节点/dev/fb*拿到访问句柄,然后通过ioctl命令访问设备句柄,来触发对逻辑驱动的行为。
int fb = open("/dev/fb0", O_RDWR);
2、设置显示相关属性fb_var_screeninfo/fb_fix_screeninfon:
拿到文件句柄后,我们需要先设置显示相关属性,比如 要访问的显存 分辨率,像素格式。
通过FBIOGET_VSCREENINFO的ioctl命令获取默认可变信息,修改其中需要调整部分,通过FBIOSET_VSCREENINFO设置属性;
// 可变显示信息 关键参数
struct fb_var_screeninfo {
// 在整个显存中的 需要真正显示内容的 可见分辨率(可以认为是一帧ui的分辨率,如1280x720)
__u32 xres; /* visible resolution */
__u32 yres;
// 整个显存的分辨率,注意xres_virtual》= xres
// 举例 当 xres_virtual = res yres_virtual = yres*2 ,相当于显存里面有2帧ui的buffer,通过切换yoffset来表示要显示哪一块
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
// 在整块显存中 显示的像素点 在 x方向 和 y方向的偏移(注意相当于是 fb逻辑访问的首地址计算偏移,不是在画面中的显示位置偏移)
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
// 每个像素点的的bit长度 和 每个分量排布 和 顺序,既描述每个点在内存中的表达方式
int bits_per_pixel;
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
//..省略..
};
// 获取默认的可变信息
ioctl(fb,FBIOGET_VSCREENINFO,&fb_var);
// 修改var可变参数
fb_var.xres = 1280;
fb_var.yres = 720;
// 设置可变信息
ioctl(fb,FBIOSET_VSCREENINFO,&fb_var);
设置完fb_var_screeninfo后,还需要获取fb_fix_screeninfo固定的显示参数,其中最关键的就是显存大小 和 每行的line_length。
注意这里顺序不可变,要先设置var可变参数,再获取fix固定参数,者里面的原因是fix参数可以理解为 不可设置参数,并不是不变,而它往往在驱动中是根据var参数 由图形驱动决策。 举个例子,line_length也叫stirde,表示每行占画面用的bytes,这往往和var中的xres水平分辨率和bits_per_pixel每个像素bits决定,设置var后驱动会更新它,但这里注意 由于逻辑访问内存存在对齐的约束,line_length >= xres * bits_per_pixel / 8, 多出来的部分是对齐必要的额外内存,这个策略只能由底层逻辑驱动自己按能力决策;
// 获取固定的显示参数
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
// 关键参数
struct fb_fix_screeninfo {
// 显存内存总长度
__u32 smem_len; /* Length of frame buffer mem */
// 显存每行数据的stride长度,即每行的bytes;
__u32 line_length; /* length of a line in bytes */
//..省略..
};
3、映射显存,并始绘制内容
// 通过mmap设备句柄,拿到图形的显存虚拟首地址地址
char *addr = mmap(NULL, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 直接通过虚拟地址进行绘制
当前显示的坐标是xoffset和yoffset,你可以直接往上面绘制(由于边绘边显示 可能会裂屏),也可以往其新坐标绘制再通知刷新。绘制的起始地址addrdraw = 显存首地址addr + 垂直方向搬移行数 yoffset * 每一行的字节数 line_length + 水平方向的像素偏移 xoffset * 每个像素的字节数量 bits_per_pixel / 8;
4、通知驱动刷新显示位置(linux标准PAN_DISPLAY方式)
linux提供的标准PAN_DISPLAY刷新方式,通知驱动显示按设置入参vinfo中的xoffset和yoffset进行刷新显示;
// 按vinfo中xoffset和yoffset进行刷新
ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo);
三、framebuffer驱动关键设计
1、硬件设计抽象 与 版本差异化处理
既然fb是一个硬件驱动,自然就涉及对硬件的抽象过程,如何定义 逻辑ip链路进行软件概念抽象自然是一个关键设计。同时,如果要用一个驱动代码 去 适配多款芯片 要考虑如何适配 逻辑上的差异,比如 层数量,层能力差异等,多示例的管理与通路绑定设计。
2、中断业务设计与优化
一般的显示相关驱动,无论视频还是图形,其逻辑配置动力都来源与时序显示中断,因为这样才能够和画面切换匹配。自然有很多内容切换需要在中断里面完成,保证 逻辑链路 切换生效时间点。这里就会设计中断耗时和性能的优化设计。
3、Android框架引入的fence 私有接口和Vsync
在android框架下引入buffer的fence概念,来通知fb驱动 这个buffer什么时候可以读取,由于标准的linux PAN_DISPLAY刷新方式是通过 x y坐标来通知,而不支持fence传递,android fence一般是走私有接口,这里传递fence和其他的一些私有信息(这种接口类似que帧设计,把帧送入fb,fb自己按中断决策刷新地址)。
android的SufaceFlinger系统服务,需要利用时序刷新时间来做Vsync支持,因此往往fb还要提供接口提供上次wait显示时序中断。