Android双帧缓冲 ---- 如何设置
以下内容译自:http://blog.chinaunix.net/u3/104807/showart_2162774.html
仅供参考!
Patch Framebuffer Driver of PrimeCell Color LCD Controller with Double-Buffering to Support Android’s Page-Flipping
http://www.yuan.se/?p=7
参考网址:http://pdk.android.com/online-pdk/guide/display_drivers.html
一、简要介绍
Android使用SurfaceFlinger模块来对framebuffer进行操作。每一个Surface都进行双缓冲,前端buffer用于显示,后端buffer用于drawing,一旦drawing完成,Android通过改变framebuffer的yoffset值来进行页切换。根据EGLDisplaySurface初始化过程,如果framebuffer的yres_virtual大于yres,那么framebuffer直接用于双缓冲;否则,后端buffer仅仅是简单的复制到前端buffer,这将导致在buffer-swapping时延时。因此,从性能考虑,framebuffer驱动需很好的支持double-buffering。
二、打补丁
在Android SDK中,用于QEMU模拟器的goldfish帧缓冲驱动支持double-buffering。
现在进行移植Android到一块ARM RealView板,板上的LCD控制芯片是PL111 PrimeCell,AMBA兼容的SoC外设。对比goldfish帧缓冲驱动(from Android cupcake branch source tree)和ARM LCD控制器帧缓冲驱动(from 2.6.27 vanilla kernel),可以很轻易的看出哪些是需要对ARM LCD帧缓冲驱动(kernel/drivers/video/amba-clcd.c)打上补丁,使之支持双缓冲。
1. 首先,在驱动的初始化部分,改变如下:
fb->fb.fix.ypanstep = 1;
fb->fb.var.yres_virtual = fb->panel->mode.yres * 2;
2. 然后,在clcdfb_ops声明中,添加一行代码:
.fb_pan_display = clcdfb_pan_display,
同时,定义函数clcdfb_pan_display,在其中调用clcdfb_set_start函数。
3. 在kernel/include/linux/amba/clcd.h中的clcdfb_check函数声明处,将
var->yres_virtual = var->yres = (var->yres + 1) & ~1;
改成
var->yres = (var->yres + 1) & ~1;
var->yres_virtual = var->yres * 2;
因为check操作会将yres_virtual值改成和yres一样。
4. 增加帧的大小,在kernel/arch/arm/mach-realview/core.c, 将
static unsigned long framesize = SZ_1M;
改成
static unsigned long framesize = 0x12C000;
0x12C000 = 640 * 480 * 2 * 2 (VGA, 16bpp and double-buffer),
三、页切换(page-flipping)是如何工作的
1. 在frameworks/base/libs/ui/EGLDisplaySurface.cpp中,函数EGLDisplaySurface::swapBuffers()调用FBIOPUT_VSCREENINFO ioctl()应用。
2. 在kernel/drivers/video/fbmem.c中,函数fb_ioctl()响应FBIOPUT_VSCREENINFO操作,其中调用了fb_set_var(),也调用了fbops->fb_set_par()和fb_pan_display()。在fb_pan_display()中调用了fbops->fb_pan_display()。
fbops->fb_set_par()和fbops->fb_pan_display()由AMBA CLCD帧缓冲驱动提供
(kernel/drivers/video/amba-clcd.c)。
3. 根据惯例,在fb_set_par()中实现屏的方向和分辨率的改变,在fb_pan_display()中当xoffset或
yoffset改变时re-map帧缓冲。
通过这种方法,实现了页切换。
四、关于clcdfb_set_par()问题
在amba clcd驱动中,clcdfb_set_par()关闭帧缓冲的电源和时钟源,设置时钟和缓冲地址,然后在打开帧缓冲。这种方式不适合Android的页切换,因为切换过程中,FBIOPUT_VSCREENINFO ioctl()会被频繁调用,这也将导致clcdfb_set_par()频繁调用。
Then the whole system is not operatable at all as a result. It’s obviously that at least the implementation of “clcdfb_set_par” is not written in a way that adheres to the conventions.
Or writting frame buffer implementation in this case still requires keeping the usage of
Android in mind.
解决方法是cache帧缓冲的设置,每一次clcdfb_set_par()被调用时,将新的帧缓冲设置跟cache中的比较,如果屏的orientation, resolution和其他改变不要求复位时钟,那就跳过;否则,cache新的设置并复位时钟。
五、补丁
diff -Naur kernel-2.6.27.orig/arch/arm/mach-realview/core.c kernel-2.6.27/arch/arm/mach-realview/core.c --- kernel-2.6.27.orig/arch/arm/mach-realview/core.c 2009-04-29 16:16:29.000000000 +0200 +++ kernel-2.6.27/arch/arm/mach-realview/core.c 2009-04-29 16:12:26.000000000 +0200 @@ -358,7 +358,8 @@ writel(val, sys_clcd); }
-static unsigned long framesize = SZ_1M; +/* 640*480*2*2 (VGA, 16bpp and double-buffer) required by Android */ +static unsigned long framesize = 0x12C000;
static int realview_clcd_setup(struct clcd_fb *fb) { diff -Naur kernel-2.6.27.orig/drivers/video/amba-clcd.c kernel-2.6.27/drivers/video/amba-clcd.c --- kernel-2.6.27.orig/drivers/video/amba-clcd.c 2009-04-29 16:16:58.000000000 +0200 +++ kernel-2.6.27/drivers/video/amba-clcd.c 2009-04-29 16:13:16.000000000 +0200 @@ -194,6 +194,37 @@ return ret; }
+struct fb_var_screeninfo cached_fb_var; +int is_fb_var_cached = 0; + +static int clcdfb_is_fb_changed(struct clcd_fb *fb) +{ + if (!is_fb_var_cached || + fb->fb.var.xres != cached_fb_var.xres || + fb->fb.var.yres != cached_fb_var.yres || + fb->fb.var.xres_virtual != cached_fb_var.xres_virtual || + fb->fb.var.yres_virtual != cached_fb_var.yres_virtual || + fb->fb.var.bits_per_pixel != cached_fb_var.bits_per_pixel || + fb->fb.var.grayscale != cached_fb_var.grayscale || + fb->fb.var.green.length != cached_fb_var.green.length || + fb->fb.var.left_margin != cached_fb_var.left_margin || + fb->fb.var.right_margin != cached_fb_var.right_margin || + fb->fb.var.upper_margin != cached_fb_var.upper_margin || + fb->fb.var.lower_margin != cached_fb_var.lower_margin || + fb->fb.var.hsync_len != cached_fb_var.hsync_len || + fb->fb.var.vsync_len != cached_fb_var.vsync_len || + fb->fb.var.sync != cached_fb_var.sync || + fb->fb.var.rotate != cached_fb_var.rotate) { + + cached_fb_var = fb->fb.var; + is_fb_var_cached = 1; + + return 1; + } + else + return 0; +} + static int clcdfb_set_par(struct fb_info *info) { struct clcd_fb *fb = to_clcd(info); @@ -207,22 +238,25 @@ else fb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
- fb->board->decode(fb, ®s); + if (clcdfb_is_fb_changed(fb)) { + + fb->board->decode(fb, ®s);
- clcdfb_disable(fb); + clcdfb_disable(fb);
- writel(regs.tim0, fb->regs + CLCD_TIM0); - writel(regs.tim1, fb->regs + CLCD_TIM1); - writel(regs.tim2, fb->regs + CLCD_TIM2); - writel(regs.tim3, fb->regs + CLCD_TIM3); + writel(regs.tim0, fb->regs + CLCD_TIM0); + writel(regs.tim1, fb->regs + CLCD_TIM1); + writel(regs.tim2, fb->regs + CLCD_TIM2); + writel(regs.tim3, fb->regs + CLCD_TIM3);
- clcdfb_set_start(fb); + clcdfb_set_start(fb);
- clk_set_rate(fb->clk, (1000000000 / regs.pixclock) * 1000); + clk_set_rate(fb->clk, (1000000000 / regs.pixclock) * 1000);
- fb->clcd_cntl = regs.cntl; + fb->clcd_cntl = regs.cntl;
- clcdfb_enable(fb, regs.cntl); + clcdfb_enable(fb, regs.cntl); + }
#ifdef DEBUG printk(KERN_INFO "CLCD: Registers set to/n" @@ -289,6 +323,17 @@ return regno > 255; }
+static int clcdfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct clcd_fb *fb = NULL; + + info->var = *var; + fb = to_clcd(info); + clcdfb_set_start(fb); + + return 0; +} + /* * Blank the screen if blank_mode != 0, else unblank. If blank == NULL * then the caller blanks by setting the CLUT (Color Look Up Table) to all @@ -332,6 +377,7 @@ .fb_check_var = clcdfb_check_var, .fb_set_par = clcdfb_set_par, .fb_setcolreg = clcdfb_setcolreg, + .fb_pan_display = clcdfb_pan_display, .fb_blank = clcdfb_blank, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, @@ -367,14 +413,14 @@ fb->fb.fix.type = FB_TYPE_PACKED_PIXELS; fb->fb.fix.type_aux = 0; fb->fb.fix.xpanstep = 0; - fb->fb.fix.ypanstep = 0; + fb->fb.fix.ypanstep = 1; fb->fb.fix.ywrapstep = 0; fb->fb.fix.accel = FB_ACCEL_NONE; fb->fb.var.xres = fb->panel->mode.xres; fb->fb.var.yres = fb->panel->mode.yres; fb->fb.var.xres_virtual = fb->panel->mode.xres; - fb->fb.var.yres_virtual = fb->panel->mode.yres; + fb->fb.var.yres_virtual = fb->panel->mode.yres * 2; fb->fb.var.bits_per_pixel = fb->panel->bpp; fb->fb.var.grayscale = fb->panel->grayscale; fb->fb.var.pixclock = fb->panel->mode.pixclock; diff -Naur kernel-2.6.27.orig/include/linux/amba/clcd.h kernel-2.6.27/include/linux/amba/clcd.h --- kernel-2.6.27.orig/include/linux/amba/clcd.h 2009-04-29 16:16:20.000000000 +0200 +++ kernel-2.6.27/include/linux/amba/clcd.h 2009-04-29 16:12:53.000000000 +0200 @@ -232,7 +232,9 @@ static inline int clcdfb_check(struct clcd_fb *fb, struct fb_var_screeninfo *var) { var->xres_virtual = var->xres = (var->xres + 15) & ~15; - var->yres_virtual = var->yres = (var->yres + 1) & ~1; + //var->yres_virtual = var->yres = (var->yres + 1) & ~1; + var->yres = (var->yres + 1) & ~1; + var->yres_virtual = var->yres * 2;
#define CHECK(e,l,h) (var->e < l || var->e > h) if (CHECK(right_margin, (5+1), 256) || /* back porch */
|