Android图形显示之本地窗口
Android窗口系统
我们知道Android系统采用OpenGL来绘制3D图形,OpenGL ES提供了本地窗口(NativeWindow)的概念,无论是在Android平台中还是其他平台中,只要实现OpenGL ES中的本地窗口定义的接口,就可以利用OpenGL ES来绘制图形。由于Android系统所有服务都建立在C/S模式下,因此Android系统在实现OpenGL ES的本地窗口时仍然包括两种本地窗口,服务进程端的本地窗口定义为FramebufferNativeWindow,该本地窗口直接由SurfaceFlinger管理。在应用程序进程端定义的本地创建为SurfaceTextureClient。在Android系统中,它们之间为一对多的关系,如下图所示:
每个应用程序App可以有多个窗口,即多个Surface,每个Surface所需的图形缓冲区由SurfaceFlinger进程中的BufferQueue对象负责管理,图形缓冲区个数最多可以有32个,每个图形缓冲区用GraphicBuffer来定义,应用程序在图形绘制前,请求SurfaceFlinger进程中的BufferQueue对象在内存中分配一块图形缓冲区,应用程序完成图形绘制后,由SurfaceFlinger将多个应用程序需要显示的Surface进行图形混合,混合后的图形窗口使用FramebufferNativeWindow来描述,同时将混合后的图形数据拷贝到Framebuffer的后台缓冲区中,等待渲染到显示屏上。以下就是Android的窗口系统设计模型:
FramebufferNativeWindow本地窗口所需的图形缓冲区直接从Framebuffer中分配,而Surface本地窗口所需的图形缓冲区则是从内存中分配,无论是从Framebuffer中分配还是从内存中分配,图形缓冲区的分配工作都是由Gralloc硬件抽象层完成,在Android图形显示之硬件抽象层Gralloc中详细分析了Gralloc模块,而Android图形缓冲区分配过程源码分析则分析了图形缓冲区的分配过程。SurfaceFlinger收集所有应用程序的显示需求,然后对应用程序所需显示的图形做图像混合操作,然后输出到自己的FramebufferNativeWindow本地窗口上。为了使用OpenGL ES绘制图形窗口,必须实现OpenGL ES定义的本地窗口协议NativeWindow。
从上图可以看出,Surface和FramebufferNativeWindow都继承于ANativeWindow,因此也就继承了OpenGL ES下的本地窗口定义的相关协议:ANativeWindow中定义的相关接口。下面分别对这两种类型的本地窗口进行深入分析。
FramebufferNativeWindow前面已经介绍了FramebufferNativeWindow是SurfaceFlinger服务进程维护的本地窗口,用于描述经过图形混合后的,即将渲染显示的图形窗口。FramebufferNativeWindow不仅实现了从ANativeWindow中继承下来的接口,自己还定义了一些额外属性:从上面的类继承图中可以看出,无论是NativeBuffer还是GraphicBuffer,它们都继承于ANativeWindowBuffer,ANativeWindowBuffer用于描述一块图形缓冲区buffer的属性信息,比如图形的宽,高,图像格式及该buffer的句柄等等信息。
接着为创建的2个NativeBuffer分配空间,使用Gralloc模块中的gpu来完成空间的分配过程,同时指定标志位为GRALLOC_USAGE_HW_FB,表示从系统帧缓冲区Framebuffer中分配。
在只有一个buffer的情况下,我们是直接以屏幕为画板来实时做画的,假设图中的每一个三角形或圆形都需要0.5秒为例,那么总计耗时应该是0.5*5=2.5秒,图形绘制过程如下:
对于用户来说,他将看到一个不断刷新的画面。对于图像刷新很频繁的情况,用户的体验就会更差。出现这种现象的原因就是程序直接以屏幕为绘图板,把还没有准备就绪的图像直接呈现给了用户。换句话说,如果将整幅图绘制完成以后再刷新到屏幕上,那么对于用户来说,他在任何时候看到的都是完整的图像。采用两个缓冲区绘制图形的情况如下:
既然FramebufferNativeWindow创建了两块图形缓冲区,那它是如何维护这两块图形缓冲区的呢?接下来就介绍图形缓冲区的申请过程:
上图描述了图形绘制的整个过程,SurfaceFlinger首先从FramebufferNativeWindow中申请出列一块图形buffer,然后将系统中的各个Surface的GraphicBuffer进行图形混合,将混合后的图形保存到申请所得的图形buffer中,接着将该buffer放回FramebufferNativeWindow的图形缓冲区队列中,最后将该buffer渲染到显示屏幕上。接下来介绍图形buffer如列过程:最后通过FBIOPUT_VSCREENINFO命令进入Framebuffer驱动,将图形渲染显示。