本文转载请联系EmbeddedHacker公众号。1、为什么需要双缓冲?单缓冲区会造成:屏幕撕裂(tearing),即多帧数据同时在屏幕上拼接在一起。为什么单缓冲区会导致撕裂:刷新率和帧率不一致。刷新率表示屏幕每秒可以更新多少次,例如30hz/60hz。帧率表示lcd控制器/gpu每秒可以绘制多少帧数据,比如30fps/60fps。LCDcontroller/gpu和屏幕协同显示一帧图像:在单缓冲区场景下,LCDuser和LCDcontroller/gpu总是共享同一个framebuffer,没有同步机制。LCD用户是写入器,LCD控制器/gpu是读取器。由于竞争的存在,没有读写同步机制,framebuffer中必须同时存在frameN和frameN-1的数据。这时候LCD在显示framebuffer的数据时,会看到撕裂的效果:可以通过doublebuffer+vsync解决撕裂问题。Doublebuffer,顾名思义,有2个framebuffer,其工作逻辑如下:LCDcontroller:drawfb0toscreenLCDuser:writedatatofb1LCDcontroller:drawfb1toscreenLCDuser:writedatatofb0循环。..vsync机制用来保证一帧图像可以不间断地显示在屏幕上。如何支持双缓冲?驱动程序和应用程序需要相互配合:2.编写支持双缓冲区的fbdev。驱动fbdev框图:首先梳理一下思路:要让驱动支持双缓冲需要做3件事。1.申请2x缓冲区:size=(2*width*height);fbi->screen_base=dma_alloc_wc(sfb->dev,size,&map_dma,GFP_KERNEL);2.将缓冲区相关信息保存在structfb_info->structfb_var_screeninfo中。structfb_var_screeninfo{__u32xres;/*visibleresolution*/__u32yres;__u32xres_virtual;/*virtualresolution*/__u32yres_virtual;__u32xoffset;/*offsetfromvirtualtovisible*/__u32yoffset;/*resolution*/...}xres和yres是实际LCD长度的宽度和yres;xres_virtual和yres_virtual分别是内存区域的宽度和长度;xoffset和yoffset用于指定当前使用哪个Buffer进行绘图。使用Buffer0时,xoffset=0,yoffset=0;使用Buffer1时,xoffset=0,yoffset=yres*1;3.支持切换缓冲区,具体实现ioctl:FBIOPAN_DISPLAY。pan的本意是平移,可以想象成显存上方的取景器,移动取景器可以看到不同的显示内容。实例分析:goldfishfb.cgoldfishfb.c是虚拟硬件goldfish的fbdev驱动,我们可以参考这个文件来学习如何实现双缓冲。1.分配2个缓冲区:intgoldfish_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:intgoldfish_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:staticstructfb_opsgoldfish_fb_ops={....fb_pan_display=goldfish_fb_pan_display,};intgoldfish_fb_pan_display(){...//通知lcdcontrollerwrite(fb->fb新内存地址.fix.smem_start+fb->fb.var.xres*2*var->yoffset,fb->reg_base+FB_SET_BASE);//等待LCDcontroller的vsync信号wait_event_timeout(fb->wait,fb->base_update_count!=base_update_count,HZ/15);}当LCD控制器在LCD上完整显示一帧图像,会产生一个中断,在中断中,会执行fb_pan_display中的唤醒和休眠过程。如果你想了解更多,可以阅读DRM框架中的fbdev兼容代码,它也支持双缓冲区:linux/drivers/gpu/drm/*/*_drm_fbdev.clinux/drivers/gpu/drm/drm_fb_helper.c3、编写支持双缓冲的fbdev应用驱动支持双缓冲后,必须在应用中使用。先梳理一下思路:查看是否支持双缓冲;启用双缓冲区:FBIOPUT_VSCREENINFO;更新缓冲区中的数据;通知驱动切换缓冲区:FBIOPAN_DISPLAY;等待切换完成:FBIO_WAITFORVSYNC;staticstructfb_var_screeninfovar;/*Currentvar*/staticintscreen_size;staticunsignedchar*fb_base;staticunsignedintline_width;staticunsignedintpixel_width;intmain(intargc,char**argv){inti;intret;intbuffer_num;intbuf_idx=1;char*buf_next;unsignedintcolors[]={0x00FF0000,0x0,0000x000000FF,0,0x00FFFFFF};/*0x00RRGGBB*/structtimespectime;...fd_fb=open("/dev/fb0",O_RDWR);ioctl(fd_fb,FBIOGET_FSCREENINFO,&fix);ioctl(fd_fb,FBIOGET_VSCREENINFO,&varth);line_wid=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_num=fix.smem_len/screen_size;printf("buffer_num=%d\n",buffer_num);fb_base=(unsignedchar*)mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd_fb,0);if(fb_base==(unsignedchar*)-1){printf("can'tmmap\n");return-1;}if((argv[1][0]=='s')||(buffer_num==1)){printf("singlebuffer:\n");while(1){for(i=0;i
