一、为何需要 double buffer?
single buffer 会导致:
屏幕撕裂(tearing),即在屏幕上同时看到多帧数据拼接在一起。
点击查看大图
single buffer 为何会造成撕裂:
refresh rate 和 frame rate 不一致。
refresh rate 表示的是 屏幕每秒能更新多少次显示,例如 30hz / 60hz。
点击查看大图
frame rate 表示的是 lcd controller / gpu 每秒能绘制多少帧数据,例如 30fps / 60fps。
点击查看大图
lcd controller / gpu 和 屏幕协作完成一帧图像的显示:
点击查看大图
在 single buffer 的场景下,lcd user 和 lcd controller / gpu 总是在共用同一个 framebuffer,且没有同步机制。
lcd user 是写者,lcd controller / gpu 是读者。
由于存在竞争关系且读写没有同步机制,framebuffer 里必须会发生同时存在frame n 和 frame n-1 的数据,此时 lcd 将 framebuffer 的数据显示出来时,就会看到撕裂的效果:
点击查看大图
可以通过 double buffer+vsync 解决撕裂的问题。
double buffer,顾名思义,就是有 2 个 framebuffer,其工作逻辑如下:
lcd controller : draw fb0 to screen
lcd user : write data to fb1
lcd controller : draw fb1 to screen
lcd user : write data to fb0
循环。..
vsync 机制则用于确保一帧图像能不被打断地显示在屏幕。
如何支持 double buffer?
需要驱动和应用互相配合:
二、编写支持 double buffer 的 fbdev 驱动
fbdev 框图:
先梳理一下思路:
让驱动支持 double buffer 需要做 3 件事。
1. 申请2 x buffer:
size = (2 * width * height);
fbi-》screen_base = dma_alloc_wc(sfb-》dev, size, &map_dma, gfp_kernel);
2. 将 buffer 相关的信息保存 struct fb_info-》 struct fb_var_screeninfo。
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
。..
}
点击查看大图
xres 和 yres 是真实的 lcd 分辨率的宽和长;
xres_virtual 和 yres_virtual 是显存区域的宽和长;
xoffset 和 yoffset 用于指定当前使用哪一个 buffer 进行绘制。使用 buffer0 时 ,xoffset = 0,yoffset=0; 使用 buffer1 时,xoffset = 0, yoffset = yres * 1;
3. 支持切换 buffer,具体的就是实现 ioctl:fbiopan_display。
pan 的本意是平移,可以想象成显存上方有一个取景框,平移取景框可以看到不同的显示内容。
实例分析:goldfishfb.c
goldfishfb.c 是虚拟硬件 goldfish 的 fbdev 驱动,我们可以参考这个文件,学习如何实现 double buffer。
1. 分配 2 x buffer:
int goldfish_fb_probe()
{
。..
framesize = width * height * 2 * 2;
fb-》fb.screen_base = (char __force __iomem *)dma_alloc_coherent(&pdev-》dev, framesize, &fbpaddr, gfp_kernel);
}
2. 设置 fb_var_screeninfo:
int goldfish_fb_probe()
{
。..
fb-》fb.var.xres = width;
fb-》fb.var.yres = height;
fb-》fb.var.xres_virtual = width;
fb-》fb.var.yres_virtual = height * 2;
}
3. 实现 ioctl / fbiopan_display:
static struct fb_ops goldfish_fb_ops = {
。..
.fb_pan_display = goldfish_fb_pan_display,
};
int goldfish_fb_pan_display()
{
。..
// 将新的显存地址告知 lcd controller
writel(fb-》fb.fix.smem_start + fb-》fb.var.xres * 2 * var-》yoffset,
fb-》reg_base + fb_set_base);
// 等待 lcd controller 的 vsync 信号
wait_event_timeout(fb-》wait,fb-》base_update_count != base_update_count, hz / 15);
}
当lcd controller 将一帧图像完整地显示在 lcd 上后,就会产生一个中断,在中断里就会执行唤醒睡眠在 fb_pan_display 里的进程。
如果你想多了解一些,可以阅读 drm 框架里的 fbdev 兼容代码,此代码也是支持 double buffer的:
linux/drivers/gpu/drm/*/*_drm_fbdev.c
linux/drivers/gpu/drm/drm_fb_helper.c
三、编写支持 double buffer 的 fbdev 应用
驱动支持 double buffer 后,还得在应用程序里将其使用起来。
先梳理一下思路:
检查是否支持 double buffer;
使能 double buffer:fbioput_vscreeninfo;
更新 buffer 里数据;
通知驱动切换 buffer:fbiopan_display;
等待切换完成:fbio_waitforvsync;
实例分析:show_color.c
static int fd_fb;
static struct fb_fix_screeninfo fix; /* current fix */
static struct fb_var_screeninfo var; /* current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
int main(int argc, char **argv)
{
int i;
int ret;
int buffer_num;
int buf_idx = 1;
char *buf_next;
unsigned int colors[] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0, 0x00ffffff}; /* 0x00rrggbb */
struct timespec time;
。..
fd_fb = open(“/dev/fb0”, o_rdwr);
ioctl(fd_fb, fbioget_fscreeninfo, &fix);
ioctl(fd_fb, fbioget_vscreeninfo, &var);
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
// 1. 获得 buffer 个数
buffer_num = fix.smem_len / screen_size;
printf(“buffer_num = %d
”, buffer_num);
fb_base = (unsigned char *)mmap(null , fix.smem_len, prot_read | prot_write, map_shared, fd_fb, 0);
if (fb_base == (unsigned char *)-1) {
printf(“can‘t mmap
”);
return -1;
}
if ((argv[1][0] == ’s‘) || (buffer_num == 1)) {
printf(“single buffer:
”);
while (1) {
for (i = 0; i 《 sizeof(colors)/sizeof(colors[0]); i++) {
lcd_draw_screen(fb_base, colors[i]);
nanosleep(&time, null);
}
}
} else {
printf(“double buffer:
”);
// 2. 使能多 buffer
var.yres_virtual = buffer_num * var.yres;
ioctl(fd_fb, fbioput_vscreeninfo, &var);
while (1) {
for (i = 0; i 《 sizeof(colors)/sizeof(colors[0]); i++) {
// 3. 更新 buffer 里的数据
buf_next = fb_base + buf_idx * screen_size;
lcd_draw_screen(buf_next, colors[i]);
// 4. 通知驱动切换 buffer
var.yoffset = buf_idx * var.yres;
ret = ioctl(fd_fb, fbiopan_display, &var);
if (ret 《 0) {
perror(“ioctl() / fbiopan_display”);
}
// 5. 等待帧同步完成
ret = 0;
ioctl(fd_fb, fbio_waitforvsync, &ret);
if (ret 《 0) {
perror(“ioctl() / fbio_waitforvsync”);
}
buf_idx = !buf_idx;
nanosleep(&time, null);
}
}
}
munmap(fb_base , screen_size);
close(fd_fb);
return 0;
}
运行:
$ 。/show_color single
buffer_num = 1
single buffer:
$ 。/show_color double
buffer_num = 2
double buffer:
该程序会在屏幕上循环的显示不同的颜色。
当传入 “single” 参数时,使用单 buffer,可见撕裂。
当传入 “double” 参数时,使用双 buffer,不再撕裂。
代码不是很复杂,我就不再详细分析了。
如果你想多了解一些,可以阅读开源软件 sdl-1.2 里的 sdl_fbvideo.c,此代码也支持了 double buffer。
概述屏蔽电缆与非屏蔽电缆之间的区别
Oclaro发布1060nm DFB 种子激光器
节能电机 节能电机和普通电机的区别
AMD锐龙5000G系列台式处理器今日开售
中国最容易和最难被GPT所代替的TOP25职业!
double buffer的fbdev驱动与应用
锂电行业的新技术和大事件介绍
运动控制器在网络技术方面的应用
智能医疗的发展将使得在未来拥有更大的空间
【紫光同创国产FPGA教程】【第二十七章】千兆以太网视频传输实验
技术分享:PCB板层布局与EMC
水压传感器工作原理及参数
LPWAN领域叫板NB-IoT和LoRa的“异类” 预言是否实现?
更多国家开始效仿中国 采用政策扶持与财政补贴加速电动汽车行业发展
美科学家开发自愈能力的新型塑料
混合式步进电机的优缺点
国外软件开发者自制HTC Vive控制器 具有手指跟踪功能
智能语音灯声控ic,智能照明语音识别芯片方案
常用的音频功放芯片
测量薄层电阻的四探针法