单Buffer的缺点与改进方法
1. 单Buffer的缺点
-
如果APP速度很慢,可以看到它在LCD上缓慢绘制图案
-
即使APP速度很高,LCD控制器不断从Framebuffer中读取数据来显示,而APP不断把数据写入Framebuffer
-
假设APP想把LCD显示为整屏幕的蓝色、红色
-
很大几率出现这种情况:
- LCD控制器读取Framebuffer数据,读到一半时,在LCD上显示了半屏幕的蓝色
- 这是APP非常高效地把整个Framebuffer的数据都改为了红色
- LCD控制器继续读取数据,于是LCD上就会显示半屏幕蓝色、半屏幕红色
- 人眼就会感觉到屏幕闪烁、撕裂
-
2. 使用多Buffer来改进
上述两个缺点的根源是一致的:Framebuffer中的数据还没准备好整帧数据,就被LCD控制器使用了。
使用双buffer甚至多buffer可以解决这个问题:
- 假设有2个Framebuffer:FB0、FB1
- LCD控制器正在读取FB0
- APP写FB1
- 写好FB1后,让LCD控制器切换到FB1
- APP写FB0
- 写好FB0后,让LCD控制器切换到FB0
3. 内核驱动程序、APP互相配合使用多buffer
流程如下:
-
驱动:分配多个buffer
fb_info->fix.smem_len = SZ_32M; fbi->screen_base = dma_alloc_writecombine(fbi->device, fbi->fix.smem_len, (dma_addr_t *)&fbi->fix.smem_start, GFP_DMA | GFP_KERNEL);
-
驱动:保存buffer信息
fb_info->fix.smem_len // 含有总buffer大小 fb_info->var // 含有单个buffer信息
-
APP:读取buffer信息
ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix); ioctl(fd_fb, FBIOGET_VSCREENINFO, &var); // 计算是否支持多buffer,有多少个buffer screen_size = var.xres * var.yres * var.bits_per_pixel / 8; nBuffers = fix.smem_len / screen_size;
-
APP:使能多buffer
var.yres_virtual = nBuffers * var.yres; ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
-
APP:写buffer
fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); /* get buffer */ pNextBuffer = fb_base + nNextBuffer * screen_size; /* set buffer */ lcd_draw_screen(pNextBuffer, colors[i]);
-
APP:开始切换buffer
/* switch buffer */ var.yoffset = nNextBuffer * var.yres; ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
-
驱动:切换buffer
// fbmem.c fb_ioctl do_fb_ioctl fb_pan_display(info, &var); err = info->fbops->fb_pan_display(var, info) // 调用硬件相关的函数
示例:
-
APP:等待切换完成(在驱动程序中已经等待切换完成了,所以这个调用并无必要)
ret = 0; ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
多buff代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
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;
void lcd_put_pixel(void *fb_base, int x, int y, unsigned int color)
{
unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
void lcd_draw_screen(void *fb_base, unsigned int color)
{
int x, y;
for (x = 0; x < var.xres; x++)
for (y = 0; y < var.yres; y++)
lcd_put_pixel(fb_base, x, y, color);
}
/* ./multi_framebuffer_test single
* ./multi_framebuffer_test double
*/
int main(int argc, char **argv)
{
int i;
int ret;
int nBuffers;
int nNextBuffer = 1;
char *pNextBuffer;
unsigned int colors[] = {0x00FF0000, 0x0000FF00, 0x000000FF, 0, 0x00FFFFFF}; /* 0x00RRGGBB */
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 100000000;
if (argc != 2)
{
printf("Usage : %s <single|double>\n", argv[0]);
return -1;
}
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix))
{
printf("can't get fix\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
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;
nBuffers = fix.smem_len / screen_size;
printf("nBuffers = %d\n", nBuffers);
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\n");
return -1;
}
if ((argv[1][0] == 's') || (nBuffers == 1))
{
while (1)
{
/* use single buffer */
for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++)
{
lcd_draw_screen(fb_base, colors[i]);
nanosleep(&time, NULL);
}
}
}
else
{
/* use double buffer */
/* a. enable use multi buffers */
var.yres_virtual = nBuffers * var.yres;
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
while (1)
{
for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++)
{
/* get buffer */
pNextBuffer = fb_base + nNextBuffer * screen_size;
/* set buffer */
lcd_draw_screen(pNextBuffer, colors[i]);
/* switch buffer */
var.yoffset = nNextBuffer * var.yres;
ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
ret = 0;
ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
nNextBuffer = !nNextBuffer;
nanosleep(&time, NULL);
}
}
}
munmap(fb_base , screen_size);
close(fd_fb);
return 0;
}