Android Graphics architecture

来源:互联网 发布:网络考试怎么复制题目 编辑:程序博客网 时间:2024/06/06 02:31

本文内容翻译自google官方文档 http://source.android.com/devices/graphics/architecture.html

andorid图形框架中有几个重要的概念,我们在开发android视频功能时,经常会使用这些组件:
Surface, SurfaceHolder, EGLSurface, SurfaceView, GLSurfaceView, SurfaceTexture, TextureView, SurfaceFlinger

本文将介绍Android图形架构的基本构成以及它们在andorid framework,application,multimedia系统中的运作机制。描述的关键是图形数据buffer在android各相关组件中的传递。

一.low-level components 底层组件
BufferQueue and gralloc

bufferqueue连接图形数据的生产者(产生图形数据)和消费者(显示或者处理图形数据)。
生产者与消费者可以在两个不同的进程,数据传递依赖于bufferqueue.

生产者获取到一个buffer(dequeueBuffer()),制定了宽,高,pixel format与usage flags之后,再放到queue中(queueBuffer())。消费者获取到buffer(acquireBuffer()),消费数据后,把buffer返回到queue中(releaseBuffer())

目前很多Android设备已经开始支持“Sync framework”。这使得系统可以结合hardware组件对graphic data做异步的操作。例如,生产者再rendering完成之前就开始提交一系列的OpenGL ES的绘制命令并参与到输出排队buffer中。Buffer队列中设置有多道栅栏,用来同步生产者与消费者的工作。这样的一个方法可以减少buffer数据在系统中传递的延迟并提升吞吐量。

队列的一些特性,例如最大支持Hold住的数据量,取决于生产者与消费者的共同作用。

BufferQueue的责任是分配Buffer,而Buffer则用来Hold Data的部分。

gralloc HAL

真正实行buffer allocation的操作是通过gralloc来实现的,里面的alloc()方法会根据buffer的参数:宽高,pixel format与usage flags等,进行操作。

gralloc不仅仅是native heap上分配内存的另外一个方式。通常alloc取决于usage flags,例如
how often the memory will be accessed from software (CPU)
how often the memory will be accessed from hardware (GPU)
whether the memory will be used as an OpenGL ES (“GLES”) texture
whether the memory will be used by a video encoder

比如你指定format RGBA8888,并且这个buffer可以从software层进行访问,那么alloc会使用R-G-B-A的顺序创建每一个pixel.如果你指定buffer只会被hardware访问并且会作为GLES texture的组成部分,那么alloc可以使用BGRA的顺序,这样效率更高。

SurfaceFlinger and Hardware Composer
数据绘制到屏幕上,需要使用到SurfaceFlinger与Hardware Composer HAL

SurfaceFlinger

SurfaceFlinger的作用是接收来自各处的数据,进行混合编制之后,然后进行显示.

当app作为foreground时,WindowManager Service会请求SurfaceFlinger绘制一个surface。SurfaceFlinger会创建一个”layer” - 它的主要组成部分是BufferQueue。此时SurfaceFlinger扮演了消费者的角色。生产者的数据是通过WindowManager传递到app,WindowManager可以直接发送frames给SurfaceFlinger。其实可以把SurfaceFlinger理解为LayerFlinger。

对于大多数apps来说,在任何时刻,屏幕上会有三个layer。“Status Bar”是在屏幕的最上层,“navigation bar”在屏幕的底部或者侧边,另外一层是app UI layer。一些app可能拥有更多或者更少的layer。例如默认的home app,对于wallpaper有单独设置一层layer,全屏游戏的应用会隐藏status bar的layer。layer都可以单独进行更新。Status与Navigation Bar是由系统process进行绘制的。app layer是由app进行绘制的。它们之间是相互独立的。

设备显示屏以固定的频率刷新屏幕,对于手机与平板来说,通常是60fps。刷新的频率有时是不固定的。
当SurfaceFlinger接收到VSYNC刷新信号时,会遍历所有的layers,寻找可以使用的新的buffers。如果找到新的buffer,那么就获取它,否则继续使用前面获取到得buffer。SurfaceFlinger总是需要有内容可以显示,所以它会持续hold住buffer。如果没有新的buffer提交到layer上,这个layer就会被SurfaceFlinger所忽略。

一旦SurfaceFlinger收集到了可见layer的所有buffers,它会请求Hardware composer如何进行composition的操作。

Hardware Composer
Hardware Composer HAL(HWC)是从3.0开始才被引入的。它首要的目的是结合可用的硬件,判断选择最有效的方式用来composite buffers。这部分的功能通常是硬件OEM厂商实现的。

举例说明2种compossite的方式:
1.先画好App的UI,然后把StatusBar与BottomBar的部分绘制到UI之上,最后把这些组合好的buffer送给硬件进行显示。
2.分别传递三部分的Buffer,告诉硬件从不同的屏幕区域读取不同的buffer。

显然第2种方式会更有效率。因为不同的设备处理能力不一样,每一个layer是否需要旋转,是否有限制的显示位置等等问题。

HWC的工作方式是这样的:
SurfaceFlinger提供给HWC一个完整的layer list,并且询问系统:该如何处理这些layers?
HWC通过把每一个layer标记为”overlay”或者“GLES Composition”来作为应答。
SurfaceFlinger对那些标记为GLES Composition的layer,传递buffer给HWC,并且让HWC来处理。
Overlay的方式比GLES composition的方式效率要低很多,特别是Overlay的内容为transparent时。

三重显示缓冲区

普通的Double-buffering
为了避免在显示上出现断层,系统需要是double-buffered:
front buffer显示的同时,back buffer正在准备。 假设frame N正在显示,那么为了在下一个VSYNC信号中显示frame N+1,此时frame N+1将被SurfaceFlinger提前获取到。当VSYNC信号到达时,HWC会flip buffer。当app需要绘制frame N+2到buffer时,SurfaceFlinger会遍历每个layer寻找是否有新的buffer,此时没有找到任何新的buffer,因此再下一个VSYNC中再次准备显示frame N+1,过了一点时间之后,app结束了rendering frame N+2并且进入了SurfaceFlinger的队列,可是此时已经太晚了,这种双重buffer的方式效率并不够高。

更高效的Triple-buffering
在VSYNC之前,frame N正在显示,frame N+1已经composited并且准备被显示,frame N+2已经在队列中并且准备好了被SurfaceFlinger获取。当屏幕flip时,buffers可以进行rotate,并且不产生bubble。

virtual display虚拟显示设备
sufaceflinger 支持一个主显示设备(如手机屏幕),一个扩展显示设备(如电视等HDMI输出),一个或多个虚拟显示设备。

虚拟显示设备可以用于录制屏幕或者通过网络传送(比如miracast屏幕镜像功能)。
virtual displays可以使用和主显示设备一样的layers,或者有自己的设置。virtual display没有自己的VSYNC,使有主设备的VSYNC信号触发图像合成。

在老版本系统中,vitural display使用GLES合成图形数据,HWC只用于管理主显示设备的图形数据合成。android 4.4后,HWC也参与了virtual display的合成。

**Surface and SurfaceHolde**r
Surface通常作为buffer queue的生产者,同时SurfaceFlinger作为消费者。 用来显示Surface的BufferQueue通常是triple-buffering的,但是buffers却是按需分配的。

Canvas Rendering

在低版本的系统中,在软件层的渲染是通过使用Skia的图像库来实现的。如果你想要绘制一个矩形,调用一个库函数,在buffer中设置恰当的bytes数据。为了确保buffer不会在同一个时刻被两个Clients所更新,或者在显示时被写入,你必须对buffer进行加锁操作。lockCanvas()方法可以锁住buffer并且返回一个Canvas用来进行绘制。unlockCanvasAndPost()方法可以解锁buffer并且发送buffer数据给compositor进行组合。

Android围绕OpenGL ES做了修改与适应。然而,为了兼容旧的API,增加了硬件加速Canvas API。
当你为了访问Canvas而锁住Surface时,CPU渲染器会于BufferQueue的生产者建立连接,指导Surface被销毁时才回断开。大多数其他的生产者(例如GLES)可以与Surface进行断开连接与重新连接,但是Canvas-based的CPU渲染器不可以。这意味着如果你不针对一个Canvas进行加锁的话,是不可以在Surface上使用GLES进行绘制,也不可以给Surface发送来自视频解码器中的数据帧。

生产者从BufferQueue中第一次请求一个buffer时,这个buffer是初始化分配为0的。初始化是非常有必要的,避免在不同进程中出现共享数据的意外错误。当你重用一个buffer时,之前的内容还是处于显示中。如果你重复的调用lockCanvas()与unlockCanvasAndPost()却没有绘制任何内容,你将会在前一个frame与渲染的frame中进行循环。

Surface的lock/unlock代码保持了前一个渲染buffer的reference。如果你在锁定Surface的时候指定了一块dirty的区域,它会从之前的buffer中copy一份non-dirty的pixel数据过来。此时buffer将会机会对等的被SurfaceFlinger或者HWC进行处理,但是因为我们仅仅是需要进行读取的操作,所以没有必要进行互斥的访问。

不通过Canvas(non-Canvas)的方式对一个Surface直接进行绘制的主要方式是通过OpenGL ES.

SurfaceHolder

连接Surface与SurfaceView的是SurfaceHolder。最开始的想法是Surface代表了原始的数据buffer,那么SurfaceHolder是app用来追踪Surface的一些上层的信息,例如dimensions与format。
通常来说,对于一个View的任何操作都回触发SurfaceHolder。一些其他的APIs,例如MediaCodec,可以直接操作Surface本身。你也可以从SurfaceHolder中获取到Surface。
获取与设置Surface参数的APIs,例如size和format,都是通过SurfaceHolder来实现的。

EGLSurface and OpenGL ES
OpenGL ES定义了一个API用来渲染图形。为了是得GLES可以在各种平台上工作,它设计成可以和一个库进行结合的方式来工作。这个库知道如何通过操作系统创建与访问窗口。在Android中使用的库叫做EGL。如果你想要绘制textured polygons(多边形,模块),你可以使用GLES的方法。如果你想要把渲染绘制到屏幕上,你可以使用EGL的方法。

在开始使用GLES之前,你需要创建一个GL context。在EGL中,这意味着创建一个EGLContext与一个EGLSurface。GLES的操作是作用在当前的context上的,当前的context保存在当前thread中,这个context是不能传递的。这意味着你必需注意渲染的动作执行在哪个线程,并且在那个线程中的context是哪个。

EGLSurface可以通过EGL(执行pbuffer)来分配一个buffer,或者通过操作系统来做一个窗口的分配。EGL window surface是通过eglCreateWindowSurface()方法来创建的。这个方法会使用一个window object作为参数,这个window object在android上可以是一个SurfaceView,一个SurfaceTexture,一个surfaceHolder或者是一个Surface,这些对象的内部都拥有一个BufferQuueue。当你执行了那个方法调用之后,EGL创建一个新的EGLSurface对象,并把这个对象与生产者的BufferQuueue的接口建立连接。
EGL并没有提供lock/unlock的方法。你需要列出绘制的命令然后执行eglSwapBuffer()来提交当前frame。这个方法名本意是描述传统的front-back buffer的swap操作,但是实际上再后续的实现中又可能会有差异。

在同一时刻,只能有一个EGLSurface与Surface进行结合,你只能有一个生产者连接到BufferQueue,但是如果你销毁了EGLSurface,那么就与BufferQueue连接断开,此时可以允许其他组件进行连接。
一个EGLSurface在同一时刻必须只能存在于一个Thread中。但是一个Thread可以切换多个不同的EGLSurface。

讨论到EGLSurface时最通常的误解是认为这只是Surface的某个方面(例如SurfaceHolder).它们实际上是有关联却相互独立的。你可以在ELGSurface上绘制没有被Surface hold住得部分,你也可以使用Surface的时候不要用EGL。EGLSurface只为GLES提供了一个绘制的地方。

二.High-level components上层组件

SurfaceView and GLSurfaceView
Android app Framework UI是基于hierarchy中的View进行搭建的。大多数的文章都没有涉及到细节的讨论,但是了解UI组件是如何经过一系列复杂的measurement与layout然后适配到一个矩形区域是非常有意义的。当app来到forground的时候,所有可见的view对象都将被SurfaceFlinger渲染到由WindowManager创建的Surface中。Layout与rendering是执行在app的UI Thread的。

无论你又多少的layouts与Views,所有的对象都是被绘制到同一个Buffer中。无论Views是否开启了hardware-accelerated都是一样的。

SurfaceView的参数和其他的View一样,你可以设置position与size。但是讨论到render的时候,SurfaceView的contents是透明的。SurfaceView在View中只是一个透明的占位区域。

当SurfaceView可见时,WindowManager会请求SurfaceFlinger创建一个新的Surface。(这个过程不是同步的,所以需要实现Surface被创建的回调用来获取到信息)。默认情况下,新创建的Surface是在app UI Surface的下层(后面),但是这个默认的Z-ordering可以被override,设置为在app UI的上面(Top表面)。

渲染到Surface上的任何数据都是由SurfaceFlinger进行组合的,而不是由app完成。这就是SurfaceView真正牛B的地方:绘制到SurfaceView上的内容可以由单独的Thread或者Proces进行操作。你可以忽略UI Thread,但是你还是需要保持SurfaceView与Activity的生命周期一致,整个SurfaceView是和app UI还有其他被硬件加速的layer是共同协作的。

请注意,相对于BufferQueue来说,Surface是生产者,而SurfaceFlinger layer是消费者。你可以使用任何合适的方法来更新Surface,只要使得Surface可以作为BufferQueue的生产者。你可以使用Surface提供的Canvas的功能,添加一个EGLSurface并使用GLES进行绘制并且使用MeidaCodec的Video decoder对Surface进行写操作。

GLSurfaceView
GLSurfaceView提供了一些帮助类用来协助管理EGL contexts,线程间的交互,与Activity生命周期的互动。

GLSurfaceView创建一个线程用来渲染与配置EGL context。当Activity暂停时,状态会被自动清除。
在大多数情况下,GLSurfaceView是非常有用的并且可以和GLES进行协作。在某些情形下,使用它是个好的选择。

SurfaceTexture
SurfaceTexture是在Android 3.0才被引入的。和SurfaceView的概念类似(Surface与View的结合体),SurfaceTexture是Surface与GLES texture的结合体。

当你创建一个SurfaceTexture时,你会创建一个BufferQueue,app此时扮演了消费者的角色。当新的buffer被生产者加入队列时,你的app会通过onFrameAvailable()的回调得到通知。
updateTextureImage()方法会释放前面hold住得buffer,从队列中请求一个新的buffer,并执行一些EGL的方法使得buffer对于GLES来说可以作为一个external texture。

External texture(GL_TEXTURE_EXTERNAL_OES)与GLES(GL_TEXTURE_2D)创建的texture并不相同。你必须对你的渲染器做一些不同的配置,同时有些东西是不能修改的。但是最主要的是:你可以把从BufferQueue中获取到的数据直接渲染为textured模型。

你可能好奇我们如何能够确保在buffer中的数据格式是能够被GLES可以识别的。当SurfaceTexture创建了BufferQueue,它会设置消费者的usge flag为GRALLOC_USAGE_HW_TEXTURE,确保被gralloc创建的任何buffer可以被GLES所使用。

因为SurfaceTexture是需要和EGL context进行交互的,你需要注意从正确的线程中调用SurfaceTexture的方法。

每一个传递的Buffer都不仅仅是buffer本身,还包含了一个timestamp与transformation的信息。
提供transformation的信息是为了提供效率。在某些情况下,对于消费者来说,source data可能是错误的orientation,我们没有在发送数据之前就做方向矫正,而是把数据与它的角度信息一起进行传递。transformation matrix的信息可以在数据被使用的时候与其他transformation信息进行整合,这样能够最大化的减少额外的开销。

timestamp也是很有帮助的。例如,假设你连接到了一个生产者的接口上(通过setPreviewTexture()连接到Camera的输出接口),如果你想要创建一个Video,你需要为每一帧数据设置当前的timestamp,这个timestamp应该是基于frame被captured来计算的,而不是buffer被接收到得时间。这个timestamp是被camera的代码进行设置的,这样确保了timestamp更加具有连续性。

SurfaceTexture and Surface

如果你仔细查看API文档,你将会发现:对于程序来说,创建一个空白Surface的唯一方式是通过它的构造函数来实现,这个构造函数使用SufaceTexture作为唯一的参数。(在API 11之前,还没有public的surface构造器)。如果你把SurfaceTexture作为Surface与Texture的结合体,只有唯一的构造方法就会成为一个缺点。

其实SurfaceTexture可以称为GLConsumer,这能够更加准确的描述它扮演的角色:作为BufferQueue的owner与comsumer。当你从SurfaceTexture中创建一个Surface时,从SurfaceTexture的BufferQueue的角度来看,你是在创建一个对象用来作为生产者。

TextureView
TextureView是Android 4.0才开始被引入的。它是很复杂的一个View对象,它会组合View与SurfaceTexture。

SurfaceTexture被称为一个GL comsumer,它会消费图形数据的buffer并使得他们作为texture的形式存在。TextureView对SurfaceTexture做了包装,取代了响应回调的职责并负责请求新的buffer。新的buffer会导致TextureView进行invalidate的操作。当请求绘制时,TextureView使用最近接收到得buffer作为它的source data,渲染时不用考虑View的任何状态值。

你可以使用GLES对TextureView进行渲染,就像SurfaceView一样。仅仅是把SurfaceTexture传递到EGL的window创建的方法里面。然而这样做,暴露了一个隐藏的问题。

在前面我们有提到,BufferQueues可以在不同的进程中传递buffers。当使用GLES渲染TextureView的时候,producer与consumer是在同一个进程,并且他们可能会执行在同一个thread。假设我们从UI Thread快速的提交了一连串的buffers。EGL buffer swap的方法会需要从BufferQueue中执行dequeue的操作,他会一直停留直到有一个buffer可用。可是直到consumer请求一个buffer进行渲染之前,不会有任何的buffer可用,而且这件事情也发生在UI Thread。所以这就出现问题了。

解决方案是使得BufferQueue确保总是有一个可用的buffer用来进行dequeue,这样避免buffer swap会被卡住。其中一个方法是当新的buffer进行queued操作时,BufferQueue能够discard前一个queued的buffer,并且确保queue中存在的最少buffer数同时限制加入队列中得最多的buffer个数。(如果你的队列中,有三个buffers,消费者一次请求了所有的三个buffer,那么就没有buffer用来出队了,buffer swap的调用会被卡住或者失败,隐藏我们需要阻止消费者一次请求超过2个buffer。)错失Buffer通常是不希望发生的事情,因此只有在特殊情况下才允许发生,例如生产者与消费者在同一个Process。

SurfaceView or TextureView
SurfaceView与TextureView扮演了相似的角色,但在实现原理上却有着非常大的差异。选择哪个才是最好的需要知道如何进行权衡利弊。

TextureView是View hierarchy中一个普通的成员,它的行为和其他View类似,可以叠加到其他View上也可以被其他View进行重叠覆盖。你可以对它执行任意的transformation,也可以通过简单的API调用获取其中的数据内容。

TextureView的主要缺陷是composition步骤的效率。对于SurfaceView来说,数据内容是由SurfaceFlinger绘制到单独的一个layer上的,完美的实现了层叠。
对于TextureView,View的composition是由GLES执行的,同时更新TextureView的数据内容可能会导致其他View组件也出现redraw(如果view是放在TextureView的上面的话)。
在View渲染成功之后,app UI layer必须通过SurfaceFlinger与其他layer(StatusBar layer,NavigationBar layer)进行composition。
对于一个全屏的视频播放应用,还有那些UI元素全部在video layer上面的应用,SurfaceView提供了更好的性能。
前面有提到过,对于RDM保护的Video只能在上层的layer上呈现。能够播放DRM的Video Player必须使用SurfaceView。

SurfaceView and Activity Lifecycle
当使用SurfaceView的时候,渲染Surface应该使用单独的Thread而不是Main UI Thread。这会带来一些thread与activity生命周期的交互问题。

首先,对于一个拥有SurfaceView的Activity,他们有两个互相依赖的生命周期:
App onCreate/onResume/onPause
Surface created/changed/destoryed

当Activity启动时,你会按下面的顺序接收到callback:
onCreate
onResume
surfaceCreated
surfaceChanged

如果你点击back按钮,你将会收到下面的callback:
onPause
surfaceDestoryed(在Surface消失之前就会执行)

如果你点击电源按钮来关闭屏幕,你只会收到onPause(),没有surfaceDestroyed()。此时Surface仍然是存活的,渲染操作还可以继续进行。如果你有需要,还可以持续请求获取到Choreographyer的信号。如果你在锁屏的情况下旋转屏幕,然后对设备进行解锁,此时Activity会被重新启动。但是如果没有进行旋转,解锁之后还是能够获取到之前的Surface。

当在SurfaceView里面使用一个单独的渲染线程时有一个问题:这个Thread的生命周期是不是应该和Surface或者Activity进行绑定?答案取决于在屏幕关闭时你想要做的操作。有两个方法:

(1)在Activity的start/stop时对Thread做start/stop的操作。
(2)在Surface的create/destory时对thread做start/stop的操作。

方法(1)是和app的生命周期进行绑定。在onResume()的时候开启渲染线程,在onPause时停止它。这会遇到一点麻烦是:不知道何时创建于配置thread,因为Surface有时存在有时又不在。我们必须等待Surface被创建时候才能做一些线程中的初始化的操作。但是我们还不能简单的把这些操作放到surfaceCreated()的回调里面执行,因为如果surface不被重新创建的话,就没有办法触发那些初始化的操作。因此我们不要查询与保存Surface的状态,并且把这些信息传递给渲染thread。请注意,在多核的系统中,线程间传递数据需要小心谨慎,最好的方式是通过Handler message来传递Surface或者SurfaceHolder,而不仅仅是把他们放到thread里面。

方法(2)更吸引人,因为Surface与渲染的逻辑是相关联的。在Surface创建之后开启渲染Thread,这样避免了一些线程间通信的问题。Surface created/changed的消息都是先于Thread收到的。我们需要确保屏幕关闭之后,渲染操作会停止,在屏幕点亮之后操作能够恢复。可以通过简单的通知Choreographer来停止触发frame绘制的回调。仅仅只有在渲染thread正在运行,我们的onReusme方法才需要恢复那些callback。如果我们基于frame之间的时间做动画的操作,中间省略掉得一些数据是不要紧的。给出明确的pause/resume的消息是个不错的写法。

这两个选项主要关心如何设置渲染器线程以及它是否正在执行。 一个问题是当Activity被杀死时(在onPause()或onSaveInstanceState())如果知道渲染器线程状态; 在这种情况下,方法1最有效,因为在渲染器线程已经加入之后,可以在没有同步原语的情况下访问其状态。

0 0