LDC驱动单Buffer的缺点与改进方法

1. 使用单Buffer的缺点

使用单缓冲区(Single Buffering)在LCD驱动显示图片时可能会有以下几个缺点:

  1. 刷新率受限:单缓冲区意味着每次只能有一个图像或帧在屏幕上显示。这限制了屏幕的刷新率,因为每次更新屏幕内容前,都需要等待当前图像处理完成。

  2. 画面撕裂(Tearing):如果画面的更新不是在垂直同步(VSync)的边界进行,可能会导致画面撕裂现象,即屏幕上的一部分显示旧帧的内容,而另一部分显示新帧的内容。

  3. 闪烁问题:在单缓冲区中,当旧图像被新的图像替换时,可能会有短暂的屏幕闪烁,因为屏幕需要从旧图像完全过渡到新图像。

  4. 性能瓶颈:由于每次只能处理一个图像,这可能会成为性能瓶颈,尤其是在处理高分辨率或复杂图像时。如果CPU或GPU需要花费较长时间来生成新图像,那么在新图像准备好之前,屏幕上将一直显示旧图像。

  5. 用户体验下降:由于上述问题,使用单缓冲区可能会导致用户体验下降,特别是在需要流畅动画或快速响应的应用中。

  6. 资源利用率低:在单缓冲区中,图形处理单元(GPU)可能在某些时候处于空闲状态,等待CPU完成图像数据的准备,这降低了资源的利用率。

  7. 不适合动态内容:对于需要显示快速动态变化内容的应用(如视频播放或游戏),单缓冲区可能无法提供足够的性能和流畅度。

实际举例: 

  • 如果APP速度很慢,可以看到它在LCD上缓慢绘制图案

  • 即使APP速度很高,LCD控制器不断从Framebuffer中读取数据来显示,而APP不断把数据写入Framebuffer

    • 假设APP想把LCD显示为整屏幕的蓝色、红色

    • 很大几率出现这种情况:

      • LCD控制器读取Framebuffer数据,读到一半时,在LCD上显示了半屏幕的蓝色

      • 这是APP非常高效地把整个Framebuffer的数据都改为了红色

      • LCD控制器继续读取数据,于是LCD上就会显示半屏幕蓝色、半屏幕红色

      • 人眼就会感觉到屏幕闪烁、撕裂

 

2. 使用多Buffer来改进

上述两个缺点的根源是一致的:Framebuffer中的数据还没准备好整帧数据,就被LCD控制器使用了。 通常可以使用双缓冲区(Double Buffering)或三缓冲区(Triple Buffering)技术。这些技术通过使用多个缓冲区来存储图像数据,可以在一个缓冲区显示图像时,另一个缓冲区在后台准备下一个图像,从而提高性能和用户体验。

 使用双buffer甚至多buffer的步骤:

  • 假设有2个Framebuffer:FB0、FB1

  • LCD控制器正在读取FB0

  • APP写FB1

  • 写好FB1后,让LCD控制器切换到FB1

  • APP写FB0

  • 写好FB0后,让LCD控制器切换到FB0

3. 内核驱动程序、APP互相配合使用多buffer

流程如下:

驱动:分配多个buffer

这段代码的作用是为帧缓冲设备分配一块32MB大小的、适合DMA操作的、物理上连续的内存区域。screen_base 成员被设置为指向这块新分配的内存,而 smem_start 被设置为这块内存的物理地址(通过 dma_addr_t 类型)。这样的内存分配通常用于需要快速数据传输的场合,比如图形加速或视频处理。

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信息

  1. fb_info->fix.smem_len

    • 这个成员表示帧缓冲的总内存大小,即分配给帧缓冲设备的内存总量。这个值通常用于确定设备可以存储多少图形数据。
    • smem_len 是 fb_fix_screeninfo 结构体的一个成员,fb_fix_screeninfo 通过 fb_info->fix 访问,它包含了帧缓冲的固定信息,比如内存地址和屏幕信息。
  2. fb_info->var

    • var 是 fb_var_screeninfo 结构体的简称,它包含了帧缓冲的可变屏幕信息,比如屏幕分辨率、颜色深度、虚拟宽度和高度等。
    • 这个结构体定义了当前活动的显示模式,包括屏幕的大小(水平和垂直分辨率)、颜色空间、帧缓冲的行长(每行的字节数)等。

这些信息通常由驱动程序设置,并在打开帧缓冲设备、查询屏幕属性或配置显示模式时使用。例如,smem_len 可以用于确定驱动程序应为帧缓冲分配多少内存,而 var 中的信息可以用于设置屏幕分辨率或颜色格式。

fb_info->fix.smem_len  // 含有总buffer大小 
fb_info->var           // 含有单个buffer信息

APP:读取buffer信息

  1. ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);

    • 这行代码使用 ioctl 函数从文件描述符 fd_fb(它应该代表一个打开的帧缓冲设备文件)获取固定屏幕信息,并将其存储在 fix 变量中。
    • FBIOGET_FSCREENINFO 是一个命令,告诉内核获取固定屏幕信息,这个信息包括帧缓冲的物理特性,如内存布局等。
    • fix 是一个 fb_fix_screeninfo 结构体实例,它将被填充内核返回的固定屏幕信息。
  2. ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);

    • 类似地,这行代码使用 ioctl 函数获取可变屏幕信息,并将其存储在 var 变量中。
    • FBIOGET_VSCREENINFO 是另一个命令,用于获取当前屏幕的可变信息,如分辨率、颜色深度等。
    • var 是一个 fb_var_screeninfo 结构体实例,它将被填充内核返回的可变屏幕信息。
  3. screen_size = var.xres * var.yres * var.bits_per_pixel / 8;

    • 这行代码计算单个屏幕帧(buffer)的大小。计算方法是将水平分辨率(var.xres)、垂直分辨率(var.yres)和每个像素的位数(var.bits_per_pixel)相乘,然后除以8(因为1字节=8位)。
    • screen_size 表示一个屏幕帧所需的字节数。
  4. nBuffers = fix.smem_len / screen_size;

    • 这行代码通过将固定屏幕信息中报告的总内存长度(fix.smem_len)除以单个屏幕帧的大小(screen_size)来计算帧缓冲支持的缓冲区数量。
    • nBuffers 表示帧缓冲可以拥有的缓冲区数量,这通常用于多缓冲技术,如双缓冲或三缓冲,以减少屏幕刷新时的闪烁和提高性能。

总的来说,这段代码是用来确定帧缓冲设备的内存布局和容量的,这对于后续的图形渲染和屏幕更新至关重要。通过了解总的内存大小和单个屏幕帧的大小,可以确定可以创建多少个独立的缓冲区来存储图形数据。

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

这段代码继续在处理Linux帧缓冲设备的相关操作。以下是对代码的逐行解释和分析:

  1. var.yres_virtual = nBuffers * var.yres;

    • 这行代码设置 fb_var_screeninfo 结构体中的 yres_virtual 成员。yres_virtual 表示虚拟屏幕的垂直分辨率,即帧缓冲可以支持的最大垂直分辨率。
    • nBuffers 是之前计算出的缓冲区数量,var.yres 是实际屏幕的垂直分辨率。通过将这两个值相乘,可以设置一个更大的虚拟屏幕高度,这通常用于支持多缓冲技术。
    • 例如,如果 var.yres 是屏幕的实际高度,而 nBuffers 是缓冲区的数量,那么 var.yres_virtual 将被设置为屏幕高度的 nBuffers 倍。这意味着每个缓冲区将具有与实际屏幕相同的高度,但总的虚拟屏幕高度将是实际高度的多倍。
  2. ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);

    • 这行代码使用 ioctl 系统调用将修改后的 fb_var_screeninfo 结构体写回帧缓冲设备。
    • FBIOPUT_VSCREENINFO 是一个命令,告诉内核更新屏幕信息。这通常用于更改屏幕的可变属性,如分辨率、颜色深度等。
    • &var 是指向 fb_var_screeninfo 结构体的指针,它包含了要更新的新屏幕信息。

目的和效果

  • 设置虚拟屏幕高度:通过设置 yres_virtual,可以告诉内核帧缓冲设备可以支持的虚拟屏幕高度。这对于多缓冲技术非常有用,因为它允许驱动程序在不同的缓冲区之间切换,从而减少屏幕刷新时的闪烁和撕裂。
  • 更新内核屏幕信息:通过 ioctl 调用,将修改后的屏幕信息发送给内核,使得这些更改生效。这可能会影响后续的图形渲染和屏幕更新操作。

注意事项

  • 缓冲区数量nBuffers 的值应该根据实际需求和硬件能力来设置。过多的缓冲区可能会增加内存的使用,但可以提高渲染性能。
  • 内存分配:在设置 yres_virtual 之前,需要确保帧缓冲的内存已经足够大,可以支持所设置的虚拟屏幕高度。否则,可能会导致内存不足的错误。

通过这种方式,应用程序可以灵活地控制帧缓冲设备的行为,优化图形渲染和显示性能。

var.yres_virtual = nBuffers * var.yres;
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);

APP:写buffer

这段代码是用于Linux帧缓冲设备的内存映射和操作的示例。以下是对代码的逐行解释和分析:

  1. fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);

    • 这行代码使用mmap函数将文件描述符fd_fb代表的帧缓冲设备内存区域映射到用户空间。mmap函数的第一个参数是NULL,表示由系统选择映射的地址。
    • fix.smem_len是帧缓冲设备的总内存大小,由之前的ioctl调用填充到fb_fix_screeninfo结构体中。
    • PROT_READ | PROT_WRITE指定了映射内存的保护选项,这里要求映射的内存可以读写。
    • MAP_SHARED表示映射是共享的,对映射区域的修改会反映到文件描述符对应的内存中。
    • fd_fb是帧缓冲设备的文件描述符。
    • 0是文件中的偏移量,通常设置为0表示从文件的开始处映射。
    • 这行代码执行后,fb_base将指向一个指向帧缓冲内存的指针。
  2. /* get buffer */

    • 这是一条注释,说明接下来的代码将获取一个缓冲区。
  3. pNextBuffer = fb_base + nNextBuffer * screen_size;

    • 这行代码通过计算来获取下一个要操作的缓冲区的地址。fb_base是帧缓冲内存的基地址,nNextBuffer是要获取的缓冲区的索引,screen_size是单个屏幕帧的大小(之前计算过)。
    • pNextBuffer是指向所选缓冲区的指针。
  4. /* set buffer */

    • 这是一条注释,说明接下来的代码将设置或绘制到所选的缓冲区。
  5. lcd_draw_screen(pNextBuffer, colors[i]);

    • 这行代码调用了lcd_draw_screen函数,该函数可能是用于将图像或屏幕内容绘制到帧缓冲的指定缓冲区。
    • pNextBuffer是目标缓冲区的地址。
    • colors[i]可能是一个颜色数组中的索引,用于指定绘制操作的颜色。

注意事项

  • mmap调用可能会失败,返回MAP_FAILED(通常是(void *)-1),因此通常需要检查mmap的返回值并适当处理错误。
  • mmap返回的内存的任何写入都直接影响硬件帧缓冲,因此可以立即在屏幕上看到结果,但同时也需要小心同步和并发访问问题。
  • lcd_draw_screen函数的实现细节未给出,但可以推测它负责实际的绘制工作,可能是将像素数据复制到缓冲区或调用其他图形库的函数。

通过这段代码,应用程序可以有效地访问和控制Linux帧缓冲设备,实现自定义的屏幕绘制和更新操作。

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

这段注释描述的是Linux内核中帧缓冲设备驱动程序的一部分代码流程,具体是关于处理ioctl请求的函数。以下是对代码流程的解释:

  1. fbmem.c:这是源文件的名称,表明这段代码位于名为fbmem.c的文件中,通常这个文件包含帧缓冲内存管理相关的代码。

  2. fb_ioctl:这很可能是一个函数,用于处理针对帧缓冲设备的ioctl请求。ioctl是一种用于发送或接收设备特定信息的系统调用。

  3. do_fb_ioctl:这是一个函数,可能是fb_ioctl函数内部的一个处理函数,用于执行实际的ioctl操作。

  4. fb_pan_display(info, &var);:这行代码调用了fb_pan_display函数,这个函数的作用是处理屏幕的平移(或称为“卷动”)操作。info是一个指向帧缓冲信息结构的指针,var是一个指向fb_var_screeninfo结构的指针,包含了屏幕的可变信息。

  5. err = info->fbops->fb_pan_display(var, info);:这行代码通过info结构中的fbops成员调用了实际的硬件相关平移显示函数。fbops是一个函数指针数组,指向各种帧缓冲操作。fb_pan_display是这个数组中的一个函数指针,它被用来执行实际的屏幕平移操作。

    • var:包含了新的屏幕平移参数。
    • info:提供了帧缓冲的上下文信息,如设备特定信息。

代码分析

  • 这段代码的流程是:首先定义了fb_ioctl函数来响应ioctl请求,然后在fb_ioctl内部调用了do_fb_ioctl函数来进一步处理这些请求。
  • do_fb_ioctl函数中,根据ioctl请求的类型,可能会调用不同的处理函数。在这个特定的例子中,调用了fb_pan_display函数来处理屏幕平移。
  • fb_pan_display函数本身并不直接执行硬件操作,而是通过调用info->fbops->fb_pan_display将请求委托给硬件相关的函数来实现。这是一种常见的设计模式,在Linux设备驱动中用于将通用操作委托给硬件特定的实现。
// 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);

4. 具体代码分析

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值