天天看點

單Buffer的缺點和改進方法單Buffer的缺點使用多Buffer來改進核心驅動程式、App互相配合使用多bufferIMX6ULL的framebuffer驅動的學習參考代碼

單Buffer的缺點和改進方法單Buffer的缺點使用多Buffer來改進核心驅動程式、App互相配合使用多bufferIMX6ULL的framebuffer驅動的學習參考代碼

應用程式會把要顯示的内容寫到緩存區中,LCD的控制器會把要顯示的内容,螢幕上的每個像素對應一個

單Buffer的缺點

  1. 如果App速度很慢,可以看到它在LCD上緩慢繪制圖案
  2. 即使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的缺點使用多Buffer來改進核心驅動程式、App互相配合使用多bufferIMX6ULL的framebuffer驅動的學習參考代碼

①驅動:配置設定多個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的缺點和改進方法單Buffer的缺點使用多Buffer來改進核心驅動程式、App互相配合使用多bufferIMX6ULL的framebuffer驅動的學習參考代碼

⑦驅動:切換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;	
}