做嵌入式开发,Linux应用好还是驱动好?

做嵌入式开发,Linux应用好还是驱动好?

一、为何需要 double buffer?

single buffer会导致:

文章下方附学习资源,自助领取。屏幕撕裂(tearing),即在屏幕上同时看到多帧数据拼接在一起。

8da12ec5693944298afa7d536435f4e3.png

点击查看大图

single buffer为何会造成撕裂:

refresh rate和 frame rate不一致。

refresh rate表示的是屏幕每秒能更新多少次显示,例如 30hz / 60hz。

724c139f7777e1bacfce9f09970c5d5c.png

点击查看大图

frame rate表示的是 lcd controller / gpu每秒能绘制多少帧数据,例如 30fps / 60fps。

390854d56cc56cf9c98774798054b7b1.png

点击查看大图

LCD controller / gpu和屏幕协作完成一帧图像的显示:

3d2d437ba21f07569e58e7de3136bbdb.png

点击查看大图

在 single buffer的场景下,LCD user和 LCD controller / gpu总是在共用同一个 framebuffer,且没有同步机制。

LCD user是写者,LCD controller / gpu是读者。

由于存在竞争关系且读写没有同步机制,framebuffer里必须会发生同时存在frame N和 frame N-1的数据,此时 LCD将 framebuffer的数据显示出来时,就会看到撕裂的效果:

af121df8fb1dd93898bff34d8944c550.png

点击查看大图

可以通过 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?

需要驱动和应用互相配合:

0a2b502f1c6db118c08bd360bdec923f.png

二、编写支持 double buffer的 fbdev驱动

fbdev框图:

1bac343c56c7a8cbbdd444ecb0f72ad7.png

先梳理一下思路:

让驱动支持 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 */

    ...

}

f37c2343bb0711132d2d3ebeab413d5b.png

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\n", 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\n");

        return -1;

    }

    if ((argv[1][0] == 's') || (buffer_num == 1)) {

        printf("single buffer:\n");

        while (1) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lcd_draw_screen(fb_base, colors[i]);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nanosleep(&time, NULL);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;} else {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("double buffer:\n");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 2.使能多&nbsp;buffer

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var.yres_virtual = buffer_num * var.yres;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while (1) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 3.更新&nbsp;buffer里的数据

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_next = fb_base + buf_idx * screen_size;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lcd_draw_screen(buf_next, colors[i]);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 4.通知驱动切换&nbsp;buffer

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var.yoffset = buf_idx * var.yres;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &var);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (ret < 0) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;perror("ioctl() / FBIOPAN_DISPLAY");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 5.等待帧同步完成

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret = 0;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (ret < 0) {

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;perror("ioctl() / FBIO_WAITFORVSYNC");

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buf_idx = !buf_idx;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nanosleep(&time, NULL);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;munmap(fb_base , screen_size);

&nbsp;&nbsp;&nbsp;&nbsp;close(fd_fb);

&nbsp;&nbsp;&nbsp;&nbsp;return 0;

}

运行:

$ ./show_color single

buffer_num = 1

single buffer:

$ ./show_color double

buffer_num = 2

double buffer:

该程序会在屏幕上循环的显示不同的颜色。

当传入&nbsp;"single"参数时,使用单&nbsp;buffer,可见撕裂。

当传入&nbsp;"double"参数时,使用双&nbsp;buffer,不再撕裂。

代码不是很复杂,我就不再详细分析了。

如果你想多了解一些,可以阅读开源软件&nbsp;SDL-1.2里的&nbsp;sdl_fbvideo.c,此代码也支持了&nbsp;double buffer。

另外,现在越来越多的显示设备走的是&nbsp;DRM框架,该框架自然是支持多&nbsp;buffer的。

2fe03e7460e9198fc5da40d95f9b771b.png002b6ebabc4ee4f03843f265ece63d30.png3d9b71d0079cd2cac8282976c284a2ae.png

猜你喜欢

转载自blog.csdn.net/l16756062003/article/details/125192054