應用程式會把要顯示的内容寫到緩存區中,LCD的控制器會把要顯示的内容,螢幕上的每個像素對應一個
單Buffer的缺點
- 如果App速度很慢,可以看到它在LCD上緩慢繪制圖案
- 即使App速度很高,LCD控制器不斷從Framebuffer中讀取資料來顯示,而App不斷把資料寫入Framebuffer
- 假設App想把Lcd顯示為整螢幕的藍色、紅色
- 很大幾率出現這種情況:
- LCD控制器讀取Framebuffer資料,讀到一半時,在LCD上顯示了半個螢幕的藍色
- 這是App非常高效地把整個Framebuffer地資料改為了紅色
- LCD控制器繼續讀取資料,于是LCD上就會顯示半螢幕藍色、半螢幕紅色
- 人眼就會感覺到螢幕閃爍、撕裂
LCD顯示異常分析——撕裂(tear effect)
使用多Buffer來改進
上述兩個缺點地根源是一緻的:
Framebuffer中的資料還沒準備好整幀資料,就被LCD控制器使用了。
使用雙buffer升值多buffer可以解決這一問題
思路:
- 假設有2個Framebuffer:FB0、FB1
- LCD控制器正在讀取FB0
- App寫FB1
- 寫好FB1後,讓LCD控制器切換到FB1
- App寫FB0
- 寫好FB0後,讓LCD控制器切換到FB0
核心驅動程式、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);
①驅動:配置設定多個buffer
②驅動:儲存buffer資訊
③App:讀取buffer資訊
④App:使能多buffer
⑤App:寫buffer
⑥App:開始切換buffer
⑦驅動:切換buffer
⑧App:等待切換完成(在驅動程式中已經等待切換完成了,是以這個調用并無必要)
IMX6ULL的framebuffer驅動的學習
①中imx6ull的配置設定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資訊
/* fbi->screen_base 起始位址 */
fbi->screen_base = dma_alloc_writecombine(fbi->device,
fbi->fix.smem_len, /* 虛拟位址長度 */
(dma_addr_t *)&fbi->fix.smem_start, /* 實體位址 */
GFP_DMA | GFP_KERNEL);
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 */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* 0 = color, 1 = grayscale, */
/* >1 = FOURCC */
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 */
⑦驅動:切換buffer
static int mxsfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *fb_info)
{
int ret = 0;
struct mxsfb_info *host = fb_info->par;
unsigned offset;
if (host->cur_blank != FB_BLANK_UNBLANK) {
dev_dbg(fb_info->device, "can't do pan display when fb "
"is blank\n");
return -EINVAL;
}
if (var->xoffset > 0) {
dev_dbg(fb_info->device, "x panning not supported\n");
return -EINVAL;
}
if ((var->yoffset + var->yres > var->yres_virtual)) { //判斷yoffset的有效性
dev_err(fb_info->device, "y panning exceeds\n");
return -EINVAL;
}
init_completion(&host->flip_complete);
offset = fb_info->fix.line_length * var->yoffset; //根據yoffset的值算出了framebuffer的偏移值
/* update on next VSYNC */
writel(fb_info->fix.smem_start + offset,
host->base + host->devdata->next_buf); //設定IMX6ULL的下一幀的位址
writel(CTRL1_CUR_FRAME_DONE_IRQ_EN,
host->base + LCDC_CTRL1 + REG_SET); //使能vsyncint中斷
ret = wait_for_completion_timeout(&host->flip_complete, HZ / 2); //等待中斷
if (!ret) {
dev_err(fb_info->device,
"mxs wait for pan flip timeout\n");
return -ETIMEDOUT;
}
return 0;
}
參考代碼
#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;
/**********************************************************************
* 函數名稱: lcd_put_pixel
* 功能描述: 在LCD指定位置上輸出指定顔色(描點)
* 輸入參數: x坐标,y坐标,顔色
* 輸出參數: 無
* 返 回 值: 會
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 建立
***********************************************************************/
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;
}