Linux驅(qū)動(dòng)開發(fā)之Fbdev雙緩存快速入門
本文轉(zhuǎn)載自微信公眾號(hào)「嵌入式Hacker」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系嵌入式Hacker公眾號(hào)。
一、為何需要 double buffer?
single buffer 會(huì)導(dǎo)致:
屏幕撕裂(tearing),即在屏幕上同時(shí)看到多幀數(shù)據(jù)拼接在一起。
single buffer 為何會(huì)造成撕裂:
refresh rate 和 frame rate 不一致。
refresh rate 表示的是 屏幕每秒能更新多少次顯示,例如 30hz / 60hz。
frame rate 表示的是 lcd controller / gpu 每秒能繪制多少幀數(shù)據(jù),例如 30fps / 60fps。
LCD controller / gpu 和 屏幕協(xié)作完成一幀圖像的顯示:
在 single buffer 的場(chǎng)景下,LCD user 和 LCD controller / gpu 總是在共用同一個(gè) framebuffer,且沒(méi)有同步機(jī)制。
LCD user 是寫者,LCD controller / gpu 是讀者。
由于存在競(jìng)爭(zhēng)關(guān)系且讀寫沒(méi)有同步機(jī)制,framebuffer 里必須會(huì)發(fā)生同時(shí)存在frame N 和 frame N-1 的數(shù)據(jù),此時(shí) LCD 將 framebuffer 的數(shù)據(jù)顯示出來(lái)時(shí),就會(huì)看到撕裂的效果:
可以通過(guò) double buffer+vsync 解決撕裂的問(wèn)題。
double buffer,顧名思義,就是有 2 個(gè) 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
- 循環(huán)...
vsync 機(jī)制則用于確保一幀圖像能不被打斷地顯示在屏幕。
如何支持 double buffer?
需要驅(qū)動(dòng)和應(yīng)用互相配合:
二、編寫支持 double buffer 的 fbdev 驅(qū)動(dòng)
fbdev 框圖:
先梳理一下思路:
讓驅(qū)動(dòng)支持 double buffer 需要做 3 件事。
1. 申請(qǐng)2 x buffer:
- size = (2 * width * height);
- fbi->screen_base = dma_alloc_wc(sfb->dev, size, &map_dma, GFP_KERNEL);
2. 將 buffer 相關(guān)的信息保存 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 */
- ...
- }
xres 和 yres 是真實(shí)的 LCD 分辨率的寬和長(zhǎng);
xres_virtual 和 yres_virtual 是顯存區(qū)域的寬和長(zhǎng);
xoffset 和 yoffset 用于指定當(dāng)前使用哪一個(gè) Buffer 進(jìn)行繪制。使用 Buffer0 時(shí) ,xoffset = 0,yoffset=0; 使用 Buffer1 時(shí),xoffset = 0, yoffset = yres * 1;
3. 支持切換 buffer,具體的就是實(shí)現(xiàn) ioctl:FBIOPAN_DISPLAY。
pan 的本意是平移,可以想象成顯存上方有一個(gè)取景框,平移取景框可以看到不同的顯示內(nèi)容。
實(shí)例分析:goldfishfb.c
goldfishfb.c 是虛擬硬件 goldfish 的 fbdev 驅(qū)動(dòng),我們可以參考這個(gè)文件,學(xué)習(xí)如何實(shí)現(xiàn) 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. 設(shè)置 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. 實(shí)現(xiàn) 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 信號(hào)
- wait_event_timeout(fb->wait,fb->base_update_count != base_update_count, HZ / 15);
- }
當(dāng)LCD controller 將一幀圖像完整地顯示在 LCD 上后,就會(huì)產(chǎn)生一個(gè)中斷,在中斷里就會(huì)執(zhí)行喚醒睡眠在 fb_pan_display 里的進(jìn)程。
如果你想多了解一些,可以閱讀 DRM 框架里的 fbdev 兼容代碼,此代碼也是支持 double buffer的:
- linux/drivers/gpu/drm/*/*_drm_fbdev.c
- linux/drivers/gpu/drm/drm_fb_helper.c
三、編寫支持 double buffer 的 fbdev 應(yīng)用
驅(qū)動(dòng)支持 double buffer 后,還得在應(yīng)用程序里將其使用起來(lái)。
先梳理一下思路:
- 檢查是否支持 double buffer;
- 使能 double buffer:FBIOPUT_VSCREENINFO;
- 更新 buffer 里數(shù)據(jù);
- 通知驅(qū)動(dòng)切換 buffer:FBIOPAN_DISPLAY;
- 等待切換完成:FBIO_WAITFORVSYNC;
實(shí)例分析: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 個(gè)數(shù)
- 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) {
- for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
- lcd_draw_screen(fb_base, colors[i]);
- nanosleep(&time, NULL);
- }
- }
- } else {
- printf("double buffer:\n");
- // 2. 使能多 buffer
- var.yres_virtual = buffer_num * var.yres;
- ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
- while (1) {
- for (i = 0; i < sizeof(colors)/sizeof(colors[0]); i++) {
- // 3. 更新 buffer 里的數(shù)據(jù)
- buf_next = fb_base + buf_idx * screen_size;
- lcd_draw_screen(buf_next, colors[i]);
- // 4. 通知驅(qū)動(dòng)切換 buffer
- var.yoffset = buf_idx * var.yres;
- ret = ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
- if (ret < 0) {
- perror("ioctl() / FBIOPAN_DISPLAY");
- }
- // 5. 等待幀同步完成
- ret = 0;
- ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
- if (ret < 0) {
- perror("ioctl() / FBIO_WAITFORVSYNC");
- }
- buf_idx = !buf_idx;
- nanosleep(&time, NULL);
- }
- }
- }
- munmap(fb_base , screen_size);
- close(fd_fb);
- return 0;
- }
運(yùn)行:
- $ ./show_color single
- buffer_num = 1
- single buffer:
- $ ./show_color double
- buffer_num = 2
- double buffer:
該程序會(huì)在屏幕上循環(huán)的顯示不同的顏色。
當(dāng)傳入 "single" 參數(shù)時(shí),使用單 buffer,可見撕裂。
當(dāng)傳入 "double" 參數(shù)時(shí),使用雙 buffer,不再撕裂。
代碼不是很復(fù)雜,我就不再詳細(xì)分析了。
如果你想多了解一些,可以閱讀開源軟件 SDL-1.2 里的 sdl_fbvideo.c,此代碼也支持了 double buffer。
另外,現(xiàn)在越來(lái)越多的顯示設(shè)備走的是 DRM 框架,該框架自然是支持多 buffer的。感興趣的小伙伴,自行查看下面的代碼:
https://github.com/dvdhrm/docs/blob/master/drm-howto/modeset-double-buffered.c
四、相關(guān)參考
百問(wèn)網(wǎng) / 韋東山驅(qū)動(dòng)大全教學(xué)視頻:https://www.100ask.net/detail/p_5ff2c46ce4b0c4f2bc4fa16d/8
維基百科:https://en.wikipedia.org/wiki/Screen_tearing