最新消息: 电脑我帮您提供丰富的电脑知识,编程学习,软件下载,win7系统下载。

[译]Android图形系统 II 图形架构

IT培训 admin 2浏览 0评论

[译]Android图形系统 II 图形架构

 
登录 注册
[译]Android图形系统 II 图形架构
收藏人:老匹夫  
2015-06-30 | 阅:  转:     |  分享 
 

原文:.html



名词



Display: 显示屏

HWC:Hardware Composer ,硬件合成器

HAL:Hardware Abstract Layer,硬件抽象层

Overlay plane: 叠加平面

Buffer: 缓冲区

BufferQueue:缓冲区队列

layer:层,绘图层

surface:表面

texture: 纹理

frame:帧,图像帧,视频帧,显示帧。






前言



本文描述了android系统级别的图形架构的核心元素,以及应用框架和多媒体系统如何使用这些元素。本文关注图形数据的缓冲区如何在整个系统中移动。如果你也好奇 为什么SurfaceView和TextureView的行为,或者Surface和EGLSurface间的交互,你来对地方了。



假定你熟悉android设备和应用,你无需了解应用框架的细节,本文仅提及了少数几个API,不过本文与其他公开文档没什么重叠。本文的目的让读者了解参与渲染一个输出桢的典型几个事件,因此你可以在设计时有所选择。为了做到这一点,我们由底至上描述UI 类如何工作,而不是它们的用法。



前面的几节包含了一些背景材料,因此建议不要跳过。我们从解释android图形缓冲区开始,描述合成及显示机制,转到支持数据合成的高级机制。



本文主要基于Android4.4。本文中提及的代码来自于AOSP或者Grafika,一个开源测试项目。



ANativeWindow class 是NDK提供的C++版本的Surface class。例如,你可以调用ANativeWindow_fromSurface从一个surface来获取ANativeWindow实例。如同在java中一样,你可以对其执行lock、render、unlock并post。 

实际上,基本的native window 类型,仅仅是对生产者侧的BudderQueue的一个封装。



BufferQueue and gralloc



Android图形的核心就是BufferQueue类,其职责很简单:连接 生产者(产生图形数据缓冲)到 消费者(接收图形数据缓冲以显示或者进一步处理)。生产者和消费者可以处于不同的进程。系统中,几乎所有的图形数据缓冲区的移动都依赖于BufferQueue。



基本用法很简单:生产者请求一个自由缓冲区(dequeueBuffer()),指定参数集(长、宽、高、格式以及一套用法标志)。生产者填充缓冲区然后将其返回到队列(queueBuffer())。随后,消费者获得缓冲区(acquireBuufer()),并使用其中的内容。当消费者处理完毕,将缓冲区返回到队列(releaseBuffer())。



大多数新的Android设备支持“同步框架”。在与能够异步操作图形数据的硬件组件合作市,该框架容许 系统干的很漂亮。例如:一个生产者可以提交一系列的OpenGL ES绘制命令,然后在渲染完成前就可以将输出缓冲压入队列中。缓冲区伴随一个fence,用于当内容准备好时发出信号。当buffer返回到自由缓冲区列表时,将伴随第二个fence,这样即使缓冲区的内容可能仍在使用中时,消费者也可以释放缓冲区。这种方法改善了Buffer在系统中移动的延迟和带宽。



队列的某些参数,例如所持有缓冲区的最大数据,由生产者和消费者共同决定。



BufferQueue 负责在需要时分配缓冲区。已分配缓冲区将尽量保留,除非某些参数发生改变:例如,如果请求的缓冲区尺寸变化,旧缓冲区将被释放,重新分配符合需要的缓冲区。



BufferQueue 目前总是由消费者来创建和“所有”。在Android4.3中,消费者必须与BufferQueue处于同一进程中,而生产者却可以在另外的进程中,依赖binder访问BufferQueue。不过在4.4中,实现却更加通用化了一点。



缓冲区总是通过handle来传递的。缓冲区内容不会被BufferQueue拷来拷去,因为这样移动数据非常没效率。



gralloc HAL



实际的缓冲区分配是由内存分配器gralloc完成。gralloc 通过厂商相关的HAL接口来实现。alloc(…)的参数如你所料:长、宽、高、格式以及一套用法标志。注意,这些标志值得好好关注。



gralloc分配器不仅仅是在本地堆上分配内存的一种方式。在某些情形下,分配的内存可能不是缓存一致的(cache-coherent),或者是用户空间不可访问的,这取决于这些标志,例如:




  • 软件访问这些内存的频率(CPU)
  • 硬件访问这些内存的频率(GPU)
  • 是否被用作 OpenGL ES(GLES) 纹理
  • 是否被视频解码器使用


例如,如果你的格式是RGBA8888,并且你指明这块缓冲区将由软件访问(也就是说你的应用将直接修改像素),那么gralloc将分配一块缓冲区,其像素点大小为4字节,格式为RGBA。相反,如果你指定该缓冲区只能被硬件访问,用作GLES纹理,gralloc将按照GLES的驱动来行事了:BGRA格式,非线性swizzled 布局,可选颜色格式,等等;提供硬件所期望的格式将改善性能。



特定的平台上,某些标志是有排斥性的。例如,video encoder标志可能要求 YUX像素,因此与“software access”、RGBA888就不相容。



gralloc返回缓冲区句柄 可以通过binder在进程间传递。



SurfaceFlinger and Hardware Composer



SurfaceFlinger的角色是从从多个生产者接收图形数据缓冲区,合成并将结果送到Display。早期版本 中,SurfaceFlinger将数据传送到硬件FrameBuffer(/dev/graphics/fb0),不过现在完全不一样了。



当应用来到前台,WindowManager服务向SurfaceFlinger申请一个绘图surface。SurfaceFlinger创建一个“layer”,layer的主要成员就是BufferQueue,而SurfaceFlinger就是BufferQueue的消费者。一个Binder对象从消费者侧经由WindowManager传递给应用,应用可以使用该Binder对象将帧直接发送给SurfaceFlinger。



berg:在Android4.3中,这个binder对象实际上就是Surface对象,包含一个ISurface指针,以及一个ISurfaceTexture指针。



                      
  1. 注意:WindowManager使用术语“windows”而不是“layer”,在WindowManager中“layer”代表其他东东。我们还是使用SurfaceFlinger的术语,有些人也争论说SurfaceFlinger应该为 LayerFlinger。


对于大多数应用,通常屏幕上存在三个layer:状态条、导航条,以及应用UI。 当然也有例外,HOME应用就有一个单独的用于墙纸的layer,而全屏游戏会藏起状态条。注意,每个layer都可以独立更新。



设备Display以某个固定的速率刷新,通常在手机和平板上是60HZ。为避免tear现象,因此仅在周期内刷新内容是非常重要的。系统使用VSYNC信号,保证对内容刷新的安全性。



刷新速率 可能随情况变化,某些移动设备根据当前条件,其刷新率在58fps-62fps。对于HDML 电视,理论上,刷新率为24-48HZ以便匹配视频。由于我们只能在一个刷新周期内刷新平率,因此,以200fps来提交缓冲区其实是个浪费,因为大部分帧都看不到。因此,与其在app提交缓冲区时立刻行动,SurfaceFlinger仅仅在Display准备好才被唤醒。



当VSYNC信号到达,SurfaceFlinger检查它的layer列表,看看有没有新的缓冲区。如果有,获取;否则,直接使用之前获取的缓冲区。SurfaceFlinger 总是希望有点啥可以显示,因此它总是抓着一块缓冲区。如果没有缓冲区提交到某个layer上,这个layer直接被无视。



一旦SurfaceFlinger收集到可视的几个layer的所有缓冲区时,它询问硬件合成器HWC 应该怎样执行合成。



Hardware Composer



HWC 在Android3.0中被首次引入,这些年稳步发展。其主要目的是根据可用硬件选择 最有效率的缓冲合成方式。作为一个HAL,其实现是设备相关的,通常由Display硬件的OEM来实现。



The value of this approach is easy to recognize when you consider “overlay planes.”这种方法的价值在于,容易识别出 什么时候你应该考虑采用“overlay planes”。overlay plane 的目的是在Display硬件中而不是在GPU中合成多个缓冲区。例如,如果你有一个竖直方向的手机,状态条、导航条以及应用内容这三个layer的内容存在于分离的缓冲区内。你可以 将这三个缓冲区的内容依次渲染叠加到一个新的草稿(scratch)缓冲区中,然后传递给Display硬件;更有效率的方法是:你直接将这三块缓冲区传递给Display硬件,告诉它应该将每个缓冲区显示在屏幕的哪个区域。



如你所想,不同Display处理器的能力很不同。overlay的数目、是否可以被旋转和混合、位置和overlap的限制,这些能力难于通过某个API来表达。因此,HWC的工作方式是这样的:




  • SurfaceFlinger 提供给HWC 一个完整的layer列表,然后询问:“你希望如何处理这些layer”
  • 作为回应,HWC   将每个layer标记为“overlay”或者 “GLES composition”
  • SurfaceFlinger处理任何GLES合成工作,将输出缓冲传递给HWC,然后让HWC完成剩下的工作。


由于决策代码有硬件提供商来定制,因此可能发挥硬件的最大性能。



当屏幕没什么改变时,overlay planes 可能性能低于GL合成。当overlay内容中含有透明像素且overlapping layers被混合在一起时,这种说法一定程度上正确。在这样的场合中,HWC可以选择要求GLES合成 某些或者全部的layer,然后保留合成的缓冲区。如果SurfaceFlinger回头再次请求同样的buffer列表,HWC可以仅仅继续显示之前已经合成的保留下来的草稿(scratch)缓冲区。这可以提高空闲设备的电池寿命。



Android4.4的设备通常支持四个overlay plane。如果试图合成更多的layer,会导致系统使用GLES合成部分layer; 因此,应用使用的layer数目,将对电源消耗和性能有明显的影响。



命令adb shell dumpsys SurfaceFlinger 可以查看SurfaceFlinger的详细信息,HWC的信息在输出的尾部:






 
 
 



 
 
 


 
 
 


 
 
 


 
 
 


 
 
 

typesource cropframe name
HWC[0.0,    0.0,  320.0,  240.0] |[48,  411, 1032, 1149]SurfaceView
HWC[0.0,   75.0, 1080.0, 1776.0] |[0,   75, 1080, 1776]com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC[0.0,    0.0, 1080.0,   75.0] |[0,    0, 1080,   75]StatusBar
HWC[0.0,    0.0, 1080.0,  144.0] |[0, 1776, 1080, 1920]NavigationBar
FB TARGET[    0.0,    0.0, 1080.0, 1920.0] | [    0,    0, 1080, 1920]HWC_FRAMEBUFFER_TARGET



这些信息告诉你,屏幕上有哪些layer,是否由overlay(HWC) 或者 OpenGL ES (GLES)合成,以及一些其它你可能不关注的信息。稍后,将检查“source crop”和“frame”的值。



GLES合成器输出到FB_TARGET layer 上。既然上面所有的layer都使用overlay,FC_TARGET 在当前帧中并没有使用。FB_TARGET的名字指示了它之前的角色:在没有overlay支持而使用/dev/graphics/fb0的设备中,所有的合成工作都有GLES来完成,合成的结果将写到framebuffer中。而最近的设备中,通常没有简单的framebuffer,因此FB_TARGET实际上是一个草稿(scratch)缓冲区。



                      
  1. 注意:这就是为什么旧版本的屏幕截图器不能工作的原因:它们试图读取framebuffer,而这个东东现在没有了。


overlay plane 还有另外一个重要的角色:它们是能显示DRM内容的唯一方式。DRM 保护的缓冲区不能够被SurfaceFlinger或者GLES驱动访问,这意味着,如果HWC切换到GLES合成,你的视频就没显示了。



The Need for Triple-Buffering



。。。。





上图描述了SurfaceFlinger和BufferQueue的流程,在帧期间During frame:




  1. 红色缓冲区填充,滑动到BufferQueue
  2. 当红色缓冲区离开应用后,蓝色缓冲区滑入,替代红色缓冲区
  3. 绿色缓冲区 以及阴影所示系统UI 滑入 HWC(图上SurfaceFlinger仍然有这些缓冲区,但是现在HWC 已经准备将他们用于下一个VSYNC到达的显示,通过overlay).



        系统UI进程提供了状态条和导航条,为简化起见 这些layer没有改动。因此,SurfaceFlinger仍继续使用之前获取的缓冲区。




蓝色缓冲区 由Display和BufferQueue同时引用,应用目前还不容需将其用于渲染,直到对应的同步fence发出信号。



在VSYNC到达是,下列操作同时发生:




  1. 红缓冲区 跳入 SurfaceFlinger,替代了绿色缓冲区
  2. 绿缓冲区跳入 Display,替代了蓝色缓冲区。a dotted-line green twin appears in the BufferQueue绿色虚线的双胞胎(twin) 出现在BufferQueue。



                            
    1. berg:这里可能说绿色缓冲区同时也通过dequeueBuffer操作放入了BufferQueue,参见《BufferQueue and gralloc》这一节。

  3. 蓝色缓冲区的fence 发出信号,系统中的蓝色缓冲区清空。



                            
    1. 这里的缓冲区并没有实际清空;如果你没有绘制就提交,你讲得到同样的蓝色的内容。清空 实际上是清除缓冲区的内容,应用应该在开始绘制之前做清除工作。

  4. 显示区域由 《blue+systemUI 》变为 《green+systemUI》

    。。。。




Virtual displays



SurfaceFlinder 支持一个主显示屏,以及一个“外部”显示屏,例如通过HDMI连接的电视。它还支持多个“虚拟”显示屏。虚拟显示屏 使得合成的输出在系统内部可用。虚拟显示屏可以用于录制屏幕,甚至通过网络来发送。



虚拟显示屏 可以与主屏分享同样的layer集合(layer stack),或者也可由有着自己的layer集。对于虚拟显示屏,没有VSYNC信号,因此主屏的VSYNC信号用于触发所有显示屏的合成。



早期的虚拟显示屏总是使用GLES来合成,HWC 主要管理主显示屏的合成。在Android4.4中,HWC 也开始参与虚拟现实的合成。



正如你想到的,虚拟设备的帧也是写到一个BufferQueue中。



Case study: screenrecord



我们已经有了BufferQueue和SurfaceFlinger的一些背景知识,下面检验一下。



Android4.4中引入的screenrecord 命令,容你将屏幕显示的任何东东录制为MP4文件。为了做到这一点,我们必须从SurfaceFlinger接受合成后的帧,送去视频编码器,然后将编码后的视频数据写到文件。视频编码器由一个独立的进程“mediaserver”所管理,因此我们必须将大量的图形缓冲区在系统中跨进程移动。为了更有挑战性一点,我们试图以60fps全屏录制视频。最有效完成该工作的关键是BufferQueue。



MediaCodec 类容许应用以包含原始数据的缓冲区来提交数据,也可以通过一个surface。我们稍后在讨论Surface的细节,现在,我们简单的认为它是生产者端的BufferQueue的一个封装。当screenrecord请求访问一个视频编码器时,mediaserver将创建一个BufferQueue,将自身作为消费者端,然后将其作为surface传递给生产者。



screenrecord 然后要求SurfaceFlinger 创建一个虚拟显示屏,直接镜像主显示屏,引导它将输出发送给前面由mediaserver返回的surface。注意:这个例子中,SurfaceFlinger 是生产者而不是消费者。



一旦配置完成,screenrecord 就可以坐等编码后的数据出现了。当应用绘制时,它们的缓冲区交付给SurfaceFlinger,后者将这些缓冲区合成为一块缓冲区,然后直接发送给mediaserver的视频编码器。screenrecord进程完全看不到这些帧。



Case study: Simulate Secondary Displays



WindowManager可以要求SurfaceFlinger创建一个可见的layer,SurfaceFlinger将作为该layer对应的BufferQueue的消费者。WindowManager也可能要求SurfaceFlinger 创建一个虚拟显示屏,这种情形SurfaceFlinger将作为BufferQueue的生产者。如果你上面的layer和虚拟显示屏连接在一起,会发生啥?



你创建了一个闭环,合成的屏幕将出现在一个window中。当然,这个window是合成输出的一部分,因此下一次刷新时,该合成的图像将也显示window内容。海龟背地球。。。。



Surface and SurfaceHolder






Surface类 早就包含在API 1.0中了。其描述很简单:一个原始缓冲区的巨涌,有屏幕合成器所管理。这段话早期是精确的,不过符合不了现代系统的标记。



Surface 代表了生产者侧的BufferQueue,通常由SurfaceFlinger所消费。当你在一个surface上渲染时,其最终结果放在某个缓冲区中被送到消费者。Surface并不简单的是一块你可以胡乱涂改的原始内存块。



用于显示屏Surface的BufferQueue通常被配置为三缓冲,不过缓冲区还是按需分配的、当生产者生成缓冲区过慢时(例如30fps的动画 显示在 60fps的显示屏时),队列中可能仅实际分配了两块缓冲区。这样最小化了内存消耗。你可以通过 dumpsys SurfaceFlinger  命令来查看每个layer的缓冲区分配情况。



                      
  1. type | handle | hint | flag | tr | blnd | format | source crop (l,t,r,b) | frame | name
  2. -----------+----------+------+------+----+------+-------------+--------------------------------+------------------------+------
  3. HWC | b583b3d0 | 0002 | 0000 | 00 | 0100 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | com.android.settings/com.android.settings.DevelopmentSettings
  4. HWC | b583b330 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 75.0 | 0, 0, 1080, 75 | StatusBar
  5. HWC | b583b150 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 144.0 | 0, 1776, 1080, 1920 | NavigationBar
  6. FB TARGET | b61b1880 | 0000 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | HWC_FRAMEBUFFER_TARGET
  7. Allocated buffers:
  8. 0xb583b150: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  9. 0xb583b1f0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  10. 0xb583b330: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  11. 0xb583b3d0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  12. 0xb583b4c0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  13. 0xb583b560: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  14. 0xb583b5b0: 7200.00 KiB | 1260 (1280) x 1920 | 3 | 0x00000900
  15. 0xb583b6f0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  16. 0xb61a33d0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  17. 0xb61a3790: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  18. 0xb61a3f60: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  19. 0xb61b1880: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  20. 0xb62ee1f0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  21. Total allocated (estimate): 53694.00 KB


Canvas Rendering



曾几何时,所有的渲染都是软件完成的,当前你现在还是可以这样做,skia 图形库提供了底层实现。如果你希望画一个矩形,你可以调用图形库,让图形库去修改缓冲区。为了保证缓冲区不会被两个客户端一起更新,或者 显示时还在被写入,你需要使用锁。 lockCanvas() 锁定某个缓冲区,然后返回一个Canvas 用于绘图;而unlockCanvasAndPost() 解锁缓冲区,然后将其发送给合成器。



时代在进步,当有通用目的的3D引擎的设备出现后,Android 围绕OpenGL ES来重定位。不过,为应用以及应用框架保持旧的API 可工作还是很重要的,因此开始进行 对CanvasAPI进行硬件加速。你可以看看Hardware Acceleration中的一些图,效果显著。Note in particular that while the Canvas provided to a View’s onDraw() method may be hardware-accelerated, the Canvas obtained when an app locks a Surface directly with lockCanvas() never is. 特别是,如果 Canvas 提供给View的onDraw()方法是基于硬件加速的时候,…【 没看懂】



当你为访问Canvas 而锁住一个surface时。”CPU 渲染器“ 连接到生产者侧的BufferQueue,保持,直到Surface销毁时才摧毁连接。大多素的生产者(如GLES)可以被断开后重连接到一个新的surface,不过基于Canvas的”CPU 渲染器“没有重连接能力。这意味着 ,如果你已经为canvas锁住了一个surface,你不能使用GLES绘制该surface,或者向该surface发送某个视频解码器的图像帧。



当生产者首次从BufferQueue中申请一块缓冲区时,缓冲区被分配出来,内容清零。这种初始化是必要的,以避免进程间非故意的数据共享。当你重新使用某个缓冲区时,之前的内容还在。如果你重复调用 lockCanvas() 和 unlockCanvasAndPost()且并不绘制点啥的话,你将仅仅在之前渲染过的帧中循环而已。



Surface的lock/unlock代码维护了先前渲染的缓冲区的引用。如果你在锁定surface时指明一块脏区域 ,它将从先前的缓冲区中拷贝那些“干净”的像素。这块缓冲区将公平地由由SurfaceFlinger 或者 HWC 处理;不过既然我们仅需要读取该缓冲区,这里没必要去等待排他性访问。



除了Canvas之外,应用直接绘制surface的主要方式是通过OpenGL ES。这在 EGLSurface and OpenGL ES 这一节描述。



SurfaceHolder



如果工作在surface,那就需要一个SurfaceHolder,尤其是SurfaceView 。surface代表了原始的由合成器管理的缓冲区,而SurfaceHolder由应用管理,跟踪了解高层信息(维度、格式、etc)。java语言的定义镜像了底层本地实现。有争议说这样划分没啥用,不过长期以来SurfaceHolder就已经是公共API的一部反。



总的来说,任何需要处理View的主体,都牵涉到了一个SurfaceHolder。某些其他的API,如MediaCodec,将直接操作surface。你可以很容易从SurfaceHolder获得surface的引用。



SurfaceHolder还实现了 Surface参数的读写API,如尺寸、格式。



EGLSurface and OpenGL ES



OpenGL ES定义了图形渲染的一套API,而不是定义了一套window系统。为了容许GLES 工作在各种平台,它需要与一个了解如何在操作系统上创建和访问windows的库来协作。在Android上,该库被称为EGL。如果你希望绘制纹理,你会使用GLES调用;如果你希望将你渲染结果显示到屏幕上,你应该使用EGL调用。



在你开始使用GLES任何功能前,你需要创建一个GL 上下文。在EGL中,这疑问这创建一个EGLContext和一个EGLSurface。GLES操作将应用于当前上下文中,该上下文通过线程局部存储来访问,而不是通过参数来传递。这要求你很小心 ,你的渲染代码在哪个线程执行,在那个线程上的是哪个上下文。



EGLSurface 可以使EGL分配的一块离屏(off-screen)缓冲,又称pbuffer,也可以是操作系统分配的window。EGL window surfaces 通过函数eglCreateWindowSurface()创建,该函数接收一个“window”对象作为参数,在Android,这个对象的类型可能是SurfaceView、SurfaceTexture 、SurfaceHolder 或者 Surface – 所有的这些类型底层都有一个BufferQueue。当你调用该函数时,EGL创建一个新的EGLSurface对象,并将此对象连接到该窗口对象的BufferQueue的生产者上。从这点看,EGLSurface的渲染将导致 一块缓冲区被 出队列,渲染,入队列以供消费者使用。(术语“window”指示了期望的用法,但是你需要牢记,输出并不一定要显示在屏幕上。)



EGL不提供 lock/unlock调用。相反,你发出绘制命令,然后调用eglSwapBuffer()来提交当前帧。这个方法的名称来源于传统的前后缓冲切换,但是实际的实现可能完全不同。



一个EGLSurface同一时刻只能对应于一个surface,因为你只能将一个生产者连接到某个BufferQueue;但是,如果你销毁了EGLSurface,它将从BufferQueue断开,这时候就可以容许其他生产者连接到该BufferQueue了。



一个指定的线程可以通过修改“current”来在多个EGLSurface间切换。当然一个EGLSurface一次只能作为一个线程的“current“ 。



最常见的错误是,当谈到EGLSurface时,认为它是Surface(例如 SurfaceHolder)的另外一面。这两个术语是完全不同的概念:你可以在某个没有关联到Surface的EGLSurface上绘图;你可以无需EGL就使用Surface;EGLSurface仅提供给GLES一个位置绘图而已。



ANativeWindow



Surface类使用java语言实现。其C++的等效类是ANativeWindow,由NDK 公开。你可以调用ANativeWindow_fromSurface(),从Surface中获取ANativeWindow实例,然后lock、渲染、unlock-and-post.



为了在本地码中创建一个EGL windows surface,你需要传递一个EGLNativeWindowType的实例到eglCreateWindowSurface()。EGLNativeWindowType 实际上是 ANativeWindow的同义词。



事实上,native window 的基础类型也仅仅是 生产者端的BufferQueue的封装。






 
 



 
 


 
 

C++类型Java类型
EGL nativce window surfaceEGLSurface
EGLNativeWindowType =  ANativeWindowSurface



SurfaceView and GLSurfaceView






前面我们已经探索了低层组件,现在是时候看看它们如何装配到应用直接使用的高层组件了。



Android应用框架UI 基于 View的继承体系。虽然大部分细节与本讨论无关,不过 这有助于理解:这些UI元素是如何经过一个复杂的测量和layout过程,最终被装配到一个矩形区域。所有可视的View对象 被渲染到一个SurfaceFlinger创建的Surface,这个Surface在应用切换到前台时由WindowManager设置。layout和渲染 工作是在应用的UI线程中执行的。



不管有多少layout和View,所有的东东最后被渲染到一个缓冲区,不管View是否使用了硬件加速。



SurfaceView 接收与其他View类似的参数,因此你可以传入 位置、尺寸 以及其他什么元素。不过,当SurfaceView开始渲染时,这些内容是完全透明的。SurfaceView的View部分仅仅是一个可以透视的占位。



当SurfaceView 的View 组件希望变为可视时,系统框架要求WindowManager 请求SurfaceFlinger创建一个新的surface。(这些不会同步发生,这就是为什么你需要提供一个回调以便在Surface创建完成后得到通知的原因)。默认情况下,新的surface 放在应用的UI surface之后,不过这个默认的z-order 可以通过将surface置顶而修改。



你在这个新surface上 渲染的任何东东最后都是由SurfaceFlinger 而不是应用自身来合成。这就是SurfaceView的真正威力:你获取的这个surface可以在独立的线程中或者其他进程中渲染,从而与应用UI执行的任何渲染 隔离,而且对应的buffer直接传递给SurfaceFlinger—你不能完全无视UI线程,因为你仍然需要与Activity的生命周期合作,当View的尺寸或者位置修改时,你也可能需要调整点啥 —不过,你有完全属于你一整个surface ,与应用UI和其它layer的调和将由HWC处理。



这里值得花点时间指出:新的surface 代表 生产者侧的BufferQueue,消费者则是 某个SurfaceFlinger layer。



你可以使用任何能够写入BufferQueue的机制来更新surface: 使用Surface-supplied Canvas函数;将一个EGLSurface连接到surface,然后使用GLES来绘图;配置一个MediaCodec视频解码器来写入surface。



Composition and the Hardware Scaler



现在我们的内容有点儿多了,有必要回去看看dumpsys SurfaceFlinger的信息。






 
 
 



 
 
 


 
 
 


 
 
 


 
 
 


 
 
 

typesource cropframe name
HWC[0.0,    0.0,  320.0,  240.0] |[48,  411, 1032, 1149]SurfaceView
HWC[0.0,   75.0, 1080.0, 1776.0] |[0,   75, 1080, 1776]com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC[0.0,    0.0, 1080.0,   75.0] |[0,    0, 1080,   75]StatusBar
HWC[0.0,    0.0, 1080.0,  144.0] |[0, 1776, 1080, 1920]NavigationBar
FB TARGET[    0.0,    0.0, 1080.0, 1920.0] | [    0,    0, 1080, 1920]HWC_FRAMEBUFFER_TARGET



这段信息 是在一台竖屏的nexus5手机上使用Grafika’s播放QVGA 视频时抓取的。注意,列表的顺序是由后至前,SurfaceView的Surface 在最后,而应用UIlayer在前,状态条和导航条在最前。



“source crop”指示了SurfaceFlinger 将要显示的那部分surface缓冲区。

注意 ”SurfaceView“,该layer放置了我们的视频内容,其source crop与视频的尺寸一致,该尺寸 SurfaceFlinger 是了解的,因为MediaCodec解码器 使用该尺寸的缓冲区。不过,帧矩形区域的尺寸就完全不一样了:984*738



SurfaceFlinger 通过缩放来填充帧矩形区域。如果你在同一个surface上开始播放一个不同的视频,底层的BufferQueue将自动重新分配合适的缓冲区,SurfaceFlinger也会调整source crop。如果新视频的纵横比变了,应用将需要强制re-layout以便进行匹配,这会导致WindowManager告诉SurfaceFlinger刷新帧矩形。



如果你使用其他方式如GLES 来渲染surface,你可以使用SurfaceHolder#setFixedSize() 来设置surface 尺寸。举例来讲,你也可以配置一个游戏总是以1280x720进行渲染,这将显著降低像素数量,需要在 2560x1440 的平板或者4K显示时,显示处理器将负责进行缩放。



GLSurfaceView



GLSurfaceView类提供了一些辅助类,帮助管理EGL上下文,线程间通信,以及与Activity 生命周期的交互。仅此而已。为使用GLES,GLSurfaceView也并不是必须的。



例如,GLSurfaceView创建了一个线程,用于渲染和配置EGL上下文。在Activity 暂停时,该线程将自动清除。大多数应用不必了解EGL 就可以GLES和GLSurfaceView。

大多数情形下,GLSurfaceView 非常有用,可以让使用GLES更容易。如果有帮助,用;没有,不理它。



SurfaceTexture






SurfaceTexture 是一个在Android3中引入的较新的类。正如 SurfaceView 是Surface和View的结合体,SurfaceTexture 是 Surface 和 GLES 纹理的结合体。



SurfaceTexture and Surface



Case Study: Grafika’s “Continuous Capture” Activity





TextureView






SurfaceView or TextureView?



Case Study: Grafika’s Play Video (TextureView)



Case Study: Grafika’s Double Decode



Conclusion






Appendix A: Game Loops






Queue Stuffing



Choreographer



Thread Management



Appendix B: SurfaceView and the Activity Lifecycle



Appendix C: Tracking BufferQueue with systrace

 
来自: 老匹夫 > 
转藏到我的图书馆 献花( 0) 分享到微信 以文找文
分享:
       人人网
        开心网
        搜狐微博
  推荐给朋友
  举报
 
类似文章 更多
  • Android核心分析(25)------Android GD...
  • 【转】Android核心分析--Andoird GDI之基....
  • Android核心分析(23)-----Andoird GDI...
  • [imx6 ]Android4.4.3--surfaceflinger导...
  • android4.4 browser渲染机制浅析
  • Android 如何使用GPU硬件加速
  • Android
  • Android中的Surface和SurfaceView
热门推广
猜你喜欢
遇到这样五类下属或者员工,果断放弃... 治愈一切腰痛的秘方 拥抱你的内在小孩 20件事让身体越来越差 速效减肥操:只要每天都坚持锻炼10分...
妙笔生花:古今奇联佳对赏析 生存的狼道:狼族哲学 应试作文的审题方法 有童心的摄影师 成功的一半是"等待"
发表评论:
  最新文章
  • 一个fork的面试题 | 酷 壳
  • vfork 挂掉的一个问题 | 酷 壳
  • linux进程创建:fork、vfork和clone联...
  • Linux进程学习总结 fork()和vfork()...
  • Android 5.0(Lollipop)事件输入系统(I...
  • Linux中的常用内存问题检测工具
更多 热门文章
  • 高一高二学习差,高三如何快速提高成...
  • 如此美味的煎饼,整个人都要沦陷了(...
  • 送给东北朋友的二人转播放器
  • 土豪茶叶蛋居然有这么多种做法
  • 这个方子,可以让人每天晚上“幸福”!
  • 窍门丨自制腐竹,这都行!?
  • 赵忠祥教你做老北京炸酱面
  • 蒸菜的家常做法 美味又健康的蒸菜
  • 如何提高自己的沟通能力
  • 优美语句:桂花雨,雨漫天,一瓣一瓣...
  • 中国古语:千年智慧
  • 拒绝高血压,老中医有降压秘方!
更多>>

[译]Android图形系统 II 图形架构

 
登录 注册
[译]Android图形系统 II 图形架构
收藏人:老匹夫  
2015-06-30 | 阅:  转:     |  分享 
 

原文:.html



名词



Display: 显示屏

HWC:Hardware Composer ,硬件合成器

HAL:Hardware Abstract Layer,硬件抽象层

Overlay plane: 叠加平面

Buffer: 缓冲区

BufferQueue:缓冲区队列

layer:层,绘图层

surface:表面

texture: 纹理

frame:帧,图像帧,视频帧,显示帧。






前言



本文描述了android系统级别的图形架构的核心元素,以及应用框架和多媒体系统如何使用这些元素。本文关注图形数据的缓冲区如何在整个系统中移动。如果你也好奇 为什么SurfaceView和TextureView的行为,或者Surface和EGLSurface间的交互,你来对地方了。



假定你熟悉android设备和应用,你无需了解应用框架的细节,本文仅提及了少数几个API,不过本文与其他公开文档没什么重叠。本文的目的让读者了解参与渲染一个输出桢的典型几个事件,因此你可以在设计时有所选择。为了做到这一点,我们由底至上描述UI 类如何工作,而不是它们的用法。



前面的几节包含了一些背景材料,因此建议不要跳过。我们从解释android图形缓冲区开始,描述合成及显示机制,转到支持数据合成的高级机制。



本文主要基于Android4.4。本文中提及的代码来自于AOSP或者Grafika,一个开源测试项目。



ANativeWindow class 是NDK提供的C++版本的Surface class。例如,你可以调用ANativeWindow_fromSurface从一个surface来获取ANativeWindow实例。如同在java中一样,你可以对其执行lock、render、unlock并post。 

实际上,基本的native window 类型,仅仅是对生产者侧的BudderQueue的一个封装。



BufferQueue and gralloc



Android图形的核心就是BufferQueue类,其职责很简单:连接 生产者(产生图形数据缓冲)到 消费者(接收图形数据缓冲以显示或者进一步处理)。生产者和消费者可以处于不同的进程。系统中,几乎所有的图形数据缓冲区的移动都依赖于BufferQueue。



基本用法很简单:生产者请求一个自由缓冲区(dequeueBuffer()),指定参数集(长、宽、高、格式以及一套用法标志)。生产者填充缓冲区然后将其返回到队列(queueBuffer())。随后,消费者获得缓冲区(acquireBuufer()),并使用其中的内容。当消费者处理完毕,将缓冲区返回到队列(releaseBuffer())。



大多数新的Android设备支持“同步框架”。在与能够异步操作图形数据的硬件组件合作市,该框架容许 系统干的很漂亮。例如:一个生产者可以提交一系列的OpenGL ES绘制命令,然后在渲染完成前就可以将输出缓冲压入队列中。缓冲区伴随一个fence,用于当内容准备好时发出信号。当buffer返回到自由缓冲区列表时,将伴随第二个fence,这样即使缓冲区的内容可能仍在使用中时,消费者也可以释放缓冲区。这种方法改善了Buffer在系统中移动的延迟和带宽。



队列的某些参数,例如所持有缓冲区的最大数据,由生产者和消费者共同决定。



BufferQueue 负责在需要时分配缓冲区。已分配缓冲区将尽量保留,除非某些参数发生改变:例如,如果请求的缓冲区尺寸变化,旧缓冲区将被释放,重新分配符合需要的缓冲区。



BufferQueue 目前总是由消费者来创建和“所有”。在Android4.3中,消费者必须与BufferQueue处于同一进程中,而生产者却可以在另外的进程中,依赖binder访问BufferQueue。不过在4.4中,实现却更加通用化了一点。



缓冲区总是通过handle来传递的。缓冲区内容不会被BufferQueue拷来拷去,因为这样移动数据非常没效率。



gralloc HAL



实际的缓冲区分配是由内存分配器gralloc完成。gralloc 通过厂商相关的HAL接口来实现。alloc(…)的参数如你所料:长、宽、高、格式以及一套用法标志。注意,这些标志值得好好关注。



gralloc分配器不仅仅是在本地堆上分配内存的一种方式。在某些情形下,分配的内存可能不是缓存一致的(cache-coherent),或者是用户空间不可访问的,这取决于这些标志,例如:




  • 软件访问这些内存的频率(CPU)
  • 硬件访问这些内存的频率(GPU)
  • 是否被用作 OpenGL ES(GLES) 纹理
  • 是否被视频解码器使用


例如,如果你的格式是RGBA8888,并且你指明这块缓冲区将由软件访问(也就是说你的应用将直接修改像素),那么gralloc将分配一块缓冲区,其像素点大小为4字节,格式为RGBA。相反,如果你指定该缓冲区只能被硬件访问,用作GLES纹理,gralloc将按照GLES的驱动来行事了:BGRA格式,非线性swizzled 布局,可选颜色格式,等等;提供硬件所期望的格式将改善性能。



特定的平台上,某些标志是有排斥性的。例如,video encoder标志可能要求 YUX像素,因此与“software access”、RGBA888就不相容。



gralloc返回缓冲区句柄 可以通过binder在进程间传递。



SurfaceFlinger and Hardware Composer



SurfaceFlinger的角色是从从多个生产者接收图形数据缓冲区,合成并将结果送到Display。早期版本 中,SurfaceFlinger将数据传送到硬件FrameBuffer(/dev/graphics/fb0),不过现在完全不一样了。



当应用来到前台,WindowManager服务向SurfaceFlinger申请一个绘图surface。SurfaceFlinger创建一个“layer”,layer的主要成员就是BufferQueue,而SurfaceFlinger就是BufferQueue的消费者。一个Binder对象从消费者侧经由WindowManager传递给应用,应用可以使用该Binder对象将帧直接发送给SurfaceFlinger。



berg:在Android4.3中,这个binder对象实际上就是Surface对象,包含一个ISurface指针,以及一个ISurfaceTexture指针。



                      
  1. 注意:WindowManager使用术语“windows”而不是“layer”,在WindowManager中“layer”代表其他东东。我们还是使用SurfaceFlinger的术语,有些人也争论说SurfaceFlinger应该为 LayerFlinger。


对于大多数应用,通常屏幕上存在三个layer:状态条、导航条,以及应用UI。 当然也有例外,HOME应用就有一个单独的用于墙纸的layer,而全屏游戏会藏起状态条。注意,每个layer都可以独立更新。



设备Display以某个固定的速率刷新,通常在手机和平板上是60HZ。为避免tear现象,因此仅在周期内刷新内容是非常重要的。系统使用VSYNC信号,保证对内容刷新的安全性。



刷新速率 可能随情况变化,某些移动设备根据当前条件,其刷新率在58fps-62fps。对于HDML 电视,理论上,刷新率为24-48HZ以便匹配视频。由于我们只能在一个刷新周期内刷新平率,因此,以200fps来提交缓冲区其实是个浪费,因为大部分帧都看不到。因此,与其在app提交缓冲区时立刻行动,SurfaceFlinger仅仅在Display准备好才被唤醒。



当VSYNC信号到达,SurfaceFlinger检查它的layer列表,看看有没有新的缓冲区。如果有,获取;否则,直接使用之前获取的缓冲区。SurfaceFlinger 总是希望有点啥可以显示,因此它总是抓着一块缓冲区。如果没有缓冲区提交到某个layer上,这个layer直接被无视。



一旦SurfaceFlinger收集到可视的几个layer的所有缓冲区时,它询问硬件合成器HWC 应该怎样执行合成。



Hardware Composer



HWC 在Android3.0中被首次引入,这些年稳步发展。其主要目的是根据可用硬件选择 最有效率的缓冲合成方式。作为一个HAL,其实现是设备相关的,通常由Display硬件的OEM来实现。



The value of this approach is easy to recognize when you consider “overlay planes.”这种方法的价值在于,容易识别出 什么时候你应该考虑采用“overlay planes”。overlay plane 的目的是在Display硬件中而不是在GPU中合成多个缓冲区。例如,如果你有一个竖直方向的手机,状态条、导航条以及应用内容这三个layer的内容存在于分离的缓冲区内。你可以 将这三个缓冲区的内容依次渲染叠加到一个新的草稿(scratch)缓冲区中,然后传递给Display硬件;更有效率的方法是:你直接将这三块缓冲区传递给Display硬件,告诉它应该将每个缓冲区显示在屏幕的哪个区域。



如你所想,不同Display处理器的能力很不同。overlay的数目、是否可以被旋转和混合、位置和overlap的限制,这些能力难于通过某个API来表达。因此,HWC的工作方式是这样的:




  • SurfaceFlinger 提供给HWC 一个完整的layer列表,然后询问:“你希望如何处理这些layer”
  • 作为回应,HWC   将每个layer标记为“overlay”或者 “GLES composition”
  • SurfaceFlinger处理任何GLES合成工作,将输出缓冲传递给HWC,然后让HWC完成剩下的工作。


由于决策代码有硬件提供商来定制,因此可能发挥硬件的最大性能。



当屏幕没什么改变时,overlay planes 可能性能低于GL合成。当overlay内容中含有透明像素且overlapping layers被混合在一起时,这种说法一定程度上正确。在这样的场合中,HWC可以选择要求GLES合成 某些或者全部的layer,然后保留合成的缓冲区。如果SurfaceFlinger回头再次请求同样的buffer列表,HWC可以仅仅继续显示之前已经合成的保留下来的草稿(scratch)缓冲区。这可以提高空闲设备的电池寿命。



Android4.4的设备通常支持四个overlay plane。如果试图合成更多的layer,会导致系统使用GLES合成部分layer; 因此,应用使用的layer数目,将对电源消耗和性能有明显的影响。



命令adb shell dumpsys SurfaceFlinger 可以查看SurfaceFlinger的详细信息,HWC的信息在输出的尾部:






 
 
 



 
 
 


 
 
 


 
 
 


 
 
 


 
 
 

typesource cropframe name
HWC[0.0,    0.0,  320.0,  240.0] |[48,  411, 1032, 1149]SurfaceView
HWC[0.0,   75.0, 1080.0, 1776.0] |[0,   75, 1080, 1776]com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC[0.0,    0.0, 1080.0,   75.0] |[0,    0, 1080,   75]StatusBar
HWC[0.0,    0.0, 1080.0,  144.0] |[0, 1776, 1080, 1920]NavigationBar
FB TARGET[    0.0,    0.0, 1080.0, 1920.0] | [    0,    0, 1080, 1920]HWC_FRAMEBUFFER_TARGET



这些信息告诉你,屏幕上有哪些layer,是否由overlay(HWC) 或者 OpenGL ES (GLES)合成,以及一些其它你可能不关注的信息。稍后,将检查“source crop”和“frame”的值。



GLES合成器输出到FB_TARGET layer 上。既然上面所有的layer都使用overlay,FC_TARGET 在当前帧中并没有使用。FB_TARGET的名字指示了它之前的角色:在没有overlay支持而使用/dev/graphics/fb0的设备中,所有的合成工作都有GLES来完成,合成的结果将写到framebuffer中。而最近的设备中,通常没有简单的framebuffer,因此FB_TARGET实际上是一个草稿(scratch)缓冲区。



                      
  1. 注意:这就是为什么旧版本的屏幕截图器不能工作的原因:它们试图读取framebuffer,而这个东东现在没有了。


overlay plane 还有另外一个重要的角色:它们是能显示DRM内容的唯一方式。DRM 保护的缓冲区不能够被SurfaceFlinger或者GLES驱动访问,这意味着,如果HWC切换到GLES合成,你的视频就没显示了。



The Need for Triple-Buffering



。。。。





上图描述了SurfaceFlinger和BufferQueue的流程,在帧期间During frame:




  1. 红色缓冲区填充,滑动到BufferQueue
  2. 当红色缓冲区离开应用后,蓝色缓冲区滑入,替代红色缓冲区
  3. 绿色缓冲区 以及阴影所示系统UI 滑入 HWC(图上SurfaceFlinger仍然有这些缓冲区,但是现在HWC 已经准备将他们用于下一个VSYNC到达的显示,通过overlay).



        系统UI进程提供了状态条和导航条,为简化起见 这些layer没有改动。因此,SurfaceFlinger仍继续使用之前获取的缓冲区。




蓝色缓冲区 由Display和BufferQueue同时引用,应用目前还不容需将其用于渲染,直到对应的同步fence发出信号。



在VSYNC到达是,下列操作同时发生:




  1. 红缓冲区 跳入 SurfaceFlinger,替代了绿色缓冲区
  2. 绿缓冲区跳入 Display,替代了蓝色缓冲区。a dotted-line green twin appears in the BufferQueue绿色虚线的双胞胎(twin) 出现在BufferQueue。



                            
    1. berg:这里可能说绿色缓冲区同时也通过dequeueBuffer操作放入了BufferQueue,参见《BufferQueue and gralloc》这一节。

  3. 蓝色缓冲区的fence 发出信号,系统中的蓝色缓冲区清空。



                            
    1. 这里的缓冲区并没有实际清空;如果你没有绘制就提交,你讲得到同样的蓝色的内容。清空 实际上是清除缓冲区的内容,应用应该在开始绘制之前做清除工作。

  4. 显示区域由 《blue+systemUI 》变为 《green+systemUI》

    。。。。




Virtual displays



SurfaceFlinder 支持一个主显示屏,以及一个“外部”显示屏,例如通过HDMI连接的电视。它还支持多个“虚拟”显示屏。虚拟显示屏 使得合成的输出在系统内部可用。虚拟显示屏可以用于录制屏幕,甚至通过网络来发送。



虚拟显示屏 可以与主屏分享同样的layer集合(layer stack),或者也可由有着自己的layer集。对于虚拟显示屏,没有VSYNC信号,因此主屏的VSYNC信号用于触发所有显示屏的合成。



早期的虚拟显示屏总是使用GLES来合成,HWC 主要管理主显示屏的合成。在Android4.4中,HWC 也开始参与虚拟现实的合成。



正如你想到的,虚拟设备的帧也是写到一个BufferQueue中。



Case study: screenrecord



我们已经有了BufferQueue和SurfaceFlinger的一些背景知识,下面检验一下。



Android4.4中引入的screenrecord 命令,容你将屏幕显示的任何东东录制为MP4文件。为了做到这一点,我们必须从SurfaceFlinger接受合成后的帧,送去视频编码器,然后将编码后的视频数据写到文件。视频编码器由一个独立的进程“mediaserver”所管理,因此我们必须将大量的图形缓冲区在系统中跨进程移动。为了更有挑战性一点,我们试图以60fps全屏录制视频。最有效完成该工作的关键是BufferQueue。



MediaCodec 类容许应用以包含原始数据的缓冲区来提交数据,也可以通过一个surface。我们稍后在讨论Surface的细节,现在,我们简单的认为它是生产者端的BufferQueue的一个封装。当screenrecord请求访问一个视频编码器时,mediaserver将创建一个BufferQueue,将自身作为消费者端,然后将其作为surface传递给生产者。



screenrecord 然后要求SurfaceFlinger 创建一个虚拟显示屏,直接镜像主显示屏,引导它将输出发送给前面由mediaserver返回的surface。注意:这个例子中,SurfaceFlinger 是生产者而不是消费者。



一旦配置完成,screenrecord 就可以坐等编码后的数据出现了。当应用绘制时,它们的缓冲区交付给SurfaceFlinger,后者将这些缓冲区合成为一块缓冲区,然后直接发送给mediaserver的视频编码器。screenrecord进程完全看不到这些帧。



Case study: Simulate Secondary Displays



WindowManager可以要求SurfaceFlinger创建一个可见的layer,SurfaceFlinger将作为该layer对应的BufferQueue的消费者。WindowManager也可能要求SurfaceFlinger 创建一个虚拟显示屏,这种情形SurfaceFlinger将作为BufferQueue的生产者。如果你上面的layer和虚拟显示屏连接在一起,会发生啥?



你创建了一个闭环,合成的屏幕将出现在一个window中。当然,这个window是合成输出的一部分,因此下一次刷新时,该合成的图像将也显示window内容。海龟背地球。。。。



Surface and SurfaceHolder






Surface类 早就包含在API 1.0中了。其描述很简单:一个原始缓冲区的巨涌,有屏幕合成器所管理。这段话早期是精确的,不过符合不了现代系统的标记。



Surface 代表了生产者侧的BufferQueue,通常由SurfaceFlinger所消费。当你在一个surface上渲染时,其最终结果放在某个缓冲区中被送到消费者。Surface并不简单的是一块你可以胡乱涂改的原始内存块。



用于显示屏Surface的BufferQueue通常被配置为三缓冲,不过缓冲区还是按需分配的、当生产者生成缓冲区过慢时(例如30fps的动画 显示在 60fps的显示屏时),队列中可能仅实际分配了两块缓冲区。这样最小化了内存消耗。你可以通过 dumpsys SurfaceFlinger  命令来查看每个layer的缓冲区分配情况。



                      
  1. type | handle | hint | flag | tr | blnd | format | source crop (l,t,r,b) | frame | name
  2. -----------+----------+------+------+----+------+-------------+--------------------------------+------------------------+------
  3. HWC | b583b3d0 | 0002 | 0000 | 00 | 0100 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | com.android.settings/com.android.settings.DevelopmentSettings
  4. HWC | b583b330 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 75.0 | 0, 0, 1080, 75 | StatusBar
  5. HWC | b583b150 | 0002 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 144.0 | 0, 1776, 1080, 1920 | NavigationBar
  6. FB TARGET | b61b1880 | 0000 | 0000 | 00 | 0105 | RGBA_8888 | 0.0, 0.0, 1080.0, 1920.0 | 0, 0, 1080, 1920 | HWC_FRAMEBUFFER_TARGET
  7. Allocated buffers:
  8. 0xb583b150: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  9. 0xb583b1f0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  10. 0xb583b330: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  11. 0xb583b3d0: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  12. 0xb583b4c0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  13. 0xb583b560: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00000900
  14. 0xb583b5b0: 7200.00 KiB | 1260 (1280) x 1920 | 3 | 0x00000900
  15. 0xb583b6f0: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  16. 0xb61a33d0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  17. 0xb61a3790: 337.50 KiB | 1080 (1152) x 75 | 1 | 0x00000900
  18. 0xb61a3f60: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  19. 0xb61b1880: 8640.00 KiB | 1080 (1152) x 1920 | 1 | 0x00001a00
  20. 0xb62ee1f0: 648.00 KiB | 1080 (1152) x 144 | 1 | 0x00000900
  21. Total allocated (estimate): 53694.00 KB


Canvas Rendering



曾几何时,所有的渲染都是软件完成的,当前你现在还是可以这样做,skia 图形库提供了底层实现。如果你希望画一个矩形,你可以调用图形库,让图形库去修改缓冲区。为了保证缓冲区不会被两个客户端一起更新,或者 显示时还在被写入,你需要使用锁。 lockCanvas() 锁定某个缓冲区,然后返回一个Canvas 用于绘图;而unlockCanvasAndPost() 解锁缓冲区,然后将其发送给合成器。



时代在进步,当有通用目的的3D引擎的设备出现后,Android 围绕OpenGL ES来重定位。不过,为应用以及应用框架保持旧的API 可工作还是很重要的,因此开始进行 对CanvasAPI进行硬件加速。你可以看看Hardware Acceleration中的一些图,效果显著。Note in particular that while the Canvas provided to a View’s onDraw() method may be hardware-accelerated, the Canvas obtained when an app locks a Surface directly with lockCanvas() never is. 特别是,如果 Canvas 提供给View的onDraw()方法是基于硬件加速的时候,…【 没看懂】



当你为访问Canvas 而锁住一个surface时。”CPU 渲染器“ 连接到生产者侧的BufferQueue,保持,直到Surface销毁时才摧毁连接。大多素的生产者(如GLES)可以被断开后重连接到一个新的surface,不过基于Canvas的”CPU 渲染器“没有重连接能力。这意味着 ,如果你已经为canvas锁住了一个surface,你不能使用GLES绘制该surface,或者向该surface发送某个视频解码器的图像帧。



当生产者首次从BufferQueue中申请一块缓冲区时,缓冲区被分配出来,内容清零。这种初始化是必要的,以避免进程间非故意的数据共享。当你重新使用某个缓冲区时,之前的内容还在。如果你重复调用 lockCanvas() 和 unlockCanvasAndPost()且并不绘制点啥的话,你将仅仅在之前渲染过的帧中循环而已。



Surface的lock/unlock代码维护了先前渲染的缓冲区的引用。如果你在锁定surface时指明一块脏区域 ,它将从先前的缓冲区中拷贝那些“干净”的像素。这块缓冲区将公平地由由SurfaceFlinger 或者 HWC 处理;不过既然我们仅需要读取该缓冲区,这里没必要去等待排他性访问。



除了Canvas之外,应用直接绘制surface的主要方式是通过OpenGL ES。这在 EGLSurface and OpenGL ES 这一节描述。



SurfaceHolder



如果工作在surface,那就需要一个SurfaceHolder,尤其是SurfaceView 。surface代表了原始的由合成器管理的缓冲区,而SurfaceHolder由应用管理,跟踪了解高层信息(维度、格式、etc)。java语言的定义镜像了底层本地实现。有争议说这样划分没啥用,不过长期以来SurfaceHolder就已经是公共API的一部反。



总的来说,任何需要处理View的主体,都牵涉到了一个SurfaceHolder。某些其他的API,如MediaCodec,将直接操作surface。你可以很容易从SurfaceHolder获得surface的引用。



SurfaceHolder还实现了 Surface参数的读写API,如尺寸、格式。



EGLSurface and OpenGL ES



OpenGL ES定义了图形渲染的一套API,而不是定义了一套window系统。为了容许GLES 工作在各种平台,它需要与一个了解如何在操作系统上创建和访问windows的库来协作。在Android上,该库被称为EGL。如果你希望绘制纹理,你会使用GLES调用;如果你希望将你渲染结果显示到屏幕上,你应该使用EGL调用。



在你开始使用GLES任何功能前,你需要创建一个GL 上下文。在EGL中,这疑问这创建一个EGLContext和一个EGLSurface。GLES操作将应用于当前上下文中,该上下文通过线程局部存储来访问,而不是通过参数来传递。这要求你很小心 ,你的渲染代码在哪个线程执行,在那个线程上的是哪个上下文。



EGLSurface 可以使EGL分配的一块离屏(off-screen)缓冲,又称pbuffer,也可以是操作系统分配的window。EGL window surfaces 通过函数eglCreateWindowSurface()创建,该函数接收一个“window”对象作为参数,在Android,这个对象的类型可能是SurfaceView、SurfaceTexture 、SurfaceHolder 或者 Surface – 所有的这些类型底层都有一个BufferQueue。当你调用该函数时,EGL创建一个新的EGLSurface对象,并将此对象连接到该窗口对象的BufferQueue的生产者上。从这点看,EGLSurface的渲染将导致 一块缓冲区被 出队列,渲染,入队列以供消费者使用。(术语“window”指示了期望的用法,但是你需要牢记,输出并不一定要显示在屏幕上。)



EGL不提供 lock/unlock调用。相反,你发出绘制命令,然后调用eglSwapBuffer()来提交当前帧。这个方法的名称来源于传统的前后缓冲切换,但是实际的实现可能完全不同。



一个EGLSurface同一时刻只能对应于一个surface,因为你只能将一个生产者连接到某个BufferQueue;但是,如果你销毁了EGLSurface,它将从BufferQueue断开,这时候就可以容许其他生产者连接到该BufferQueue了。



一个指定的线程可以通过修改“current”来在多个EGLSurface间切换。当然一个EGLSurface一次只能作为一个线程的“current“ 。



最常见的错误是,当谈到EGLSurface时,认为它是Surface(例如 SurfaceHolder)的另外一面。这两个术语是完全不同的概念:你可以在某个没有关联到Surface的EGLSurface上绘图;你可以无需EGL就使用Surface;EGLSurface仅提供给GLES一个位置绘图而已。



ANativeWindow



Surface类使用java语言实现。其C++的等效类是ANativeWindow,由NDK 公开。你可以调用ANativeWindow_fromSurface(),从Surface中获取ANativeWindow实例,然后lock、渲染、unlock-and-post.



为了在本地码中创建一个EGL windows surface,你需要传递一个EGLNativeWindowType的实例到eglCreateWindowSurface()。EGLNativeWindowType 实际上是 ANativeWindow的同义词。



事实上,native window 的基础类型也仅仅是 生产者端的BufferQueue的封装。






 
 



 
 


 
 

C++类型Java类型
EGL nativce window surfaceEGLSurface
EGLNativeWindowType =  ANativeWindowSurface



SurfaceView and GLSurfaceView






前面我们已经探索了低层组件,现在是时候看看它们如何装配到应用直接使用的高层组件了。



Android应用框架UI 基于 View的继承体系。虽然大部分细节与本讨论无关,不过 这有助于理解:这些UI元素是如何经过一个复杂的测量和layout过程,最终被装配到一个矩形区域。所有可视的View对象 被渲染到一个SurfaceFlinger创建的Surface,这个Surface在应用切换到前台时由WindowManager设置。layout和渲染 工作是在应用的UI线程中执行的。



不管有多少layout和View,所有的东东最后被渲染到一个缓冲区,不管View是否使用了硬件加速。



SurfaceView 接收与其他View类似的参数,因此你可以传入 位置、尺寸 以及其他什么元素。不过,当SurfaceView开始渲染时,这些内容是完全透明的。SurfaceView的View部分仅仅是一个可以透视的占位。



当SurfaceView 的View 组件希望变为可视时,系统框架要求WindowManager 请求SurfaceFlinger创建一个新的surface。(这些不会同步发生,这就是为什么你需要提供一个回调以便在Surface创建完成后得到通知的原因)。默认情况下,新的surface 放在应用的UI surface之后,不过这个默认的z-order 可以通过将surface置顶而修改。



你在这个新surface上 渲染的任何东东最后都是由SurfaceFlinger 而不是应用自身来合成。这就是SurfaceView的真正威力:你获取的这个surface可以在独立的线程中或者其他进程中渲染,从而与应用UI执行的任何渲染 隔离,而且对应的buffer直接传递给SurfaceFlinger—你不能完全无视UI线程,因为你仍然需要与Activity的生命周期合作,当View的尺寸或者位置修改时,你也可能需要调整点啥 —不过,你有完全属于你一整个surface ,与应用UI和其它layer的调和将由HWC处理。



这里值得花点时间指出:新的surface 代表 生产者侧的BufferQueue,消费者则是 某个SurfaceFlinger layer。



你可以使用任何能够写入BufferQueue的机制来更新surface: 使用Surface-supplied Canvas函数;将一个EGLSurface连接到surface,然后使用GLES来绘图;配置一个MediaCodec视频解码器来写入surface。



Composition and the Hardware Scaler



现在我们的内容有点儿多了,有必要回去看看dumpsys SurfaceFlinger的信息。






 
 
 



 
 
 


 
 
 


 
 
 


 
 
 


 
 
 

typesource cropframe name
HWC[0.0,    0.0,  320.0,  240.0] |[48,  411, 1032, 1149]SurfaceView
HWC[0.0,   75.0, 1080.0, 1776.0] |[0,   75, 1080, 1776]com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity
HWC[0.0,    0.0, 1080.0,   75.0] |[0,    0, 1080,   75]StatusBar
HWC[0.0,    0.0, 1080.0,  144.0] |[0, 1776, 1080, 1920]NavigationBar
FB TARGET[    0.0,    0.0, 1080.0, 1920.0] | [    0,    0, 1080, 1920]HWC_FRAMEBUFFER_TARGET



这段信息 是在一台竖屏的nexus5手机上使用Grafika’s播放QVGA 视频时抓取的。注意,列表的顺序是由后至前,SurfaceView的Surface 在最后,而应用UIlayer在前,状态条和导航条在最前。



“source crop”指示了SurfaceFlinger 将要显示的那部分surface缓冲区。

注意 ”SurfaceView“,该layer放置了我们的视频内容,其source crop与视频的尺寸一致,该尺寸 SurfaceFlinger 是了解的,因为MediaCodec解码器 使用该尺寸的缓冲区。不过,帧矩形区域的尺寸就完全不一样了:984*738



SurfaceFlinger 通过缩放来填充帧矩形区域。如果你在同一个surface上开始播放一个不同的视频,底层的BufferQueue将自动重新分配合适的缓冲区,SurfaceFlinger也会调整source crop。如果新视频的纵横比变了,应用将需要强制re-layout以便进行匹配,这会导致WindowManager告诉SurfaceFlinger刷新帧矩形。



如果你使用其他方式如GLES 来渲染surface,你可以使用SurfaceHolder#setFixedSize() 来设置surface 尺寸。举例来讲,你也可以配置一个游戏总是以1280x720进行渲染,这将显著降低像素数量,需要在 2560x1440 的平板或者4K显示时,显示处理器将负责进行缩放。



GLSurfaceView



GLSurfaceView类提供了一些辅助类,帮助管理EGL上下文,线程间通信,以及与Activity 生命周期的交互。仅此而已。为使用GLES,GLSurfaceView也并不是必须的。



例如,GLSurfaceView创建了一个线程,用于渲染和配置EGL上下文。在Activity 暂停时,该线程将自动清除。大多数应用不必了解EGL 就可以GLES和GLSurfaceView。

大多数情形下,GLSurfaceView 非常有用,可以让使用GLES更容易。如果有帮助,用;没有,不理它。



SurfaceTexture






SurfaceTexture 是一个在Android3中引入的较新的类。正如 SurfaceView 是Surface和View的结合体,SurfaceTexture 是 Surface 和 GLES 纹理的结合体。



SurfaceTexture and Surface



Case Study: Grafika’s “Continuous Capture” Activity





TextureView






SurfaceView or TextureView?



Case Study: Grafika’s Play Video (TextureView)



Case Study: Grafika’s Double Decode



Conclusion






Appendix A: Game Loops






Queue Stuffing



Choreographer



Thread Management



Appendix B: SurfaceView and the Activity Lifecycle



Appendix C: Tracking BufferQueue with systrace

 
来自: 老匹夫 > 
转藏到我的图书馆 献花( 0) 分享到微信 以文找文
分享:
       人人网
        开心网
        搜狐微博
  推荐给朋友
  举报
 
类似文章 更多
  • Android核心分析(25)------Android GD...
  • 【转】Android核心分析--Andoird GDI之基....
  • Android核心分析(23)-----Andoird GDI...
  • [imx6 ]Android4.4.3--surfaceflinger导...
  • android4.4 browser渲染机制浅析
  • Android 如何使用GPU硬件加速
  • Android
  • Android中的Surface和SurfaceView
热门推广
猜你喜欢
遇到这样五类下属或者员工,果断放弃... 治愈一切腰痛的秘方 拥抱你的内在小孩 20件事让身体越来越差 速效减肥操:只要每天都坚持锻炼10分...
妙笔生花:古今奇联佳对赏析 生存的狼道:狼族哲学 应试作文的审题方法 有童心的摄影师 成功的一半是"等待"
发表评论:
  最新文章
  • 一个fork的面试题 | 酷 壳
  • vfork 挂掉的一个问题 | 酷 壳
  • linux进程创建:fork、vfork和clone联...
  • Linux进程学习总结 fork()和vfork()...
  • Android 5.0(Lollipop)事件输入系统(I...
  • Linux中的常用内存问题检测工具
更多 热门文章
  • 高一高二学习差,高三如何快速提高成...
  • 如此美味的煎饼,整个人都要沦陷了(...
  • 送给东北朋友的二人转播放器
  • 土豪茶叶蛋居然有这么多种做法
  • 这个方子,可以让人每天晚上“幸福”!
  • 窍门丨自制腐竹,这都行!?
  • 赵忠祥教你做老北京炸酱面
  • 蒸菜的家常做法 美味又健康的蒸菜
  • 如何提高自己的沟通能力
  • 优美语句:桂花雨,雨漫天,一瓣一瓣...
  • 中国古语:千年智慧
  • 拒绝高血压,老中医有降压秘方!
更多>>

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论