| <html devsite><head> |
| <title>SurfaceFlinger 和 Hardware Composer</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p>拥有图形数据缓冲区的确不错,如果还能在设备屏幕上查看它们就更是锦上添花了。这正是 SurfaceFlinger 和 Hardware Composer HAL 的用武之地。</p> |
| |
| <h2 id="surfaceflinger">SurfaceFlinger</h2> |
| |
| <p>SurfaceFlinger 的作用是接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。以前,该过程通过软件到硬件帧缓冲区(例如 <code>/dev/graphics/fb0</code>)的位块传输来实现,但是这样的日子已经远去。</p> |
| |
| <p>当应用进入前台时,WindowManager 服务会向 SurfaceFlinger 请求一个绘图 Surface。SurfaceFlinger 会创建一个其主要组件为 BufferQueue 的层,而 SurfaceFlinger 是其消耗方。生产方端的 Binder 对象通过 WindowManager 传递到应用,然后应用可以开始直接将帧发送到 SurfaceFlinger。</p> |
| |
| <p class="note"><strong>注意:</strong>尽管本部分使用 SurfaceFlinger 术语,但 WindowManager 会使用术语“窗口”(而不是“层”)…而将“层”用来表示其他内容。<em></em><em></em>(有人认为 SurfaceFlinger 实际上应称为 LayerFlinger。)</p> |
| |
| <p>大多数应用通常在屏幕上有三个层:屏幕顶部的状态栏、底部或侧面的导航栏以及应用的界面。有些应用会拥有更多或更少的层(例如,默认主屏幕应用有一个单独的壁纸层,而全屏游戏可能会隐藏状态栏)。每个层都可以单独更新。状态栏和导航栏由系统进程渲染,而应用层由应用渲染,两者之间不进行协调。</p> |
| |
| <p>设备显示会按一定速率刷新,在手机和平板电脑上通常为每秒 60 帧。如果显示内容在刷新期间更新,则会出现撕裂现象;因此,请务必只在周期之间更新内容。在可以安全更新内容时,系统便会收到来自显示设备的信号。由于历史原因,我们将该信号称为 VSYNC 信号。</p> |
| |
| <p>刷新率可能会随时间而变化,例如,一些移动设备的刷新率范围在 58 fps 到 62 fps 之间,具体要视当前条件而定。对于连接了 HDMI 的电视,刷新率在理论上可以下降到 24 Hz 或 48 Hz,以便与视频相匹配。由于每个刷新周期只能更新屏幕一次,因此以 200 fps 的刷新率为显示设备提交缓冲区只是在做无用功,因为大多数帧永远不会被看到。SurfaceFlinger 不会在应用提交缓冲区时执行操作,而是在显示设备准备好接收新的缓冲区时才会唤醒。</p> |
| |
| <p>当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 总是需要可显示的内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。</p> |
| |
| <p>SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。</p> |
| |
| <h2 id="hwc">Hardware Composer</h2> |
| |
| <p>Hardware Composer HAL (HWC) 是在 Android 3.0 中推出的,并且多年来一直都在不断演进。它主要是用来确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示设备硬件原始设备制造商 (OEM) 完成。</p> |
| |
| <p>当考虑叠加平面时,很容易发现这种方法的好处。它的目的是在显示硬件(而不是 GPU)中将多个缓冲区合成在一起。例如,假设有一部处于纵向模式的普通 Android 手机,其状态栏在顶部,导航栏在底部,其他地方为应用内容。<em></em>每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成:</p> |
| |
| <ul> |
| <li>将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。</li> |
| <li>将三个缓冲区全部传送到显示硬件,并告知它从不同的缓冲区读取屏幕不同部分的数据。</li> |
| </ul> |
| |
| <p>后一种方法可以显著提高效率。</p> |
| |
| <p>显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。HWC 会尝试通过一系列决策来适应这种多样性:</p> |
| |
| <ol> |
| <li>SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?”</li> |
| <li>HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。</li> |
| <li>SurfaceFlinger 会处理所有 GLES 合成,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。</li> |
| </ol> |
| |
| <p>由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能。</p> |
| |
| <p>当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以选择为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 返回要求合成同一组缓冲区,HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。</p> |
| |
| <p>运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成多于叠加层的层会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对功耗和性能产生重大影响。</p> |
| |
| <h2 id="virtual-displays">虚拟显示设备</h2> |
| |
| <p>SurfaceFlinger 支持一个主要显示设备(即内置在手机或平板电脑中的显示屏)、一个外部显示设备(如通过 HDMI 连接的电视)以及一个或多个令合成的输出在系统中可用的虚拟显示设备。虚拟显示设备可用于记录屏幕或通过网络发送屏幕。</p> |
| |
| <p>虚拟显示设备可以与主显示设备共享相同的一组层(层堆叠),也可拥有自己的一组层。虚拟显示设备没有 VSYNC,因此主显示设备的 VSYNC 用于为所有显示设备触发合成。</p> |
| |
| <p>在旧版本的 Android 中,虚拟显示设备总是通过 GLES 合成,而 Hardware Composer 管理的合成仅用于主要显示设备。在 Android 4.4 中,Hardware Composer 能够参与虚拟显示设备合成。</p> |
| |
| <p>正如您所预期的,为虚拟显示设备生成的帧会写入 BufferQueue。</p> |
| |
| <h2 id="screenrecord">案例研究:screenrecord</h2> |
| |
| <p><a href="https://android.googlesource.com/platform/frameworks/av/+/marshmallow-release/cmds/screenrecord/">screenrecord 命令</a>可让您将屏幕上显示的所有内容作为一个 .mp4 文件记录在磁盘上。为此,我们必须从 SurfaceFlinger 接收合成的帧,将它们写入视频编码器,然后将已编码的视频数据写入一个文件。视频编解码器由单独的进程 (mediaserver) 进行管理,因此我们必须在系统中移动大量图形缓冲区。为了使其更具挑战性,我们尝试以全分辨率录制 60 fps 的视频。高效完成这项工作的关键是 BufferQueue。</p> |
| |
| <p>MediaCodec 类允许应用以缓冲区中的原始字节或通过 <a href="/devices/graphics/arch-sh.html">Surface</a> 来提供数据。当 screenrecord 请求访问视频编码器时,mediaserver 会创建一个 BufferQueue,将其自身连接到消耗方端,然后将生产方端作为 Surface 传回到 screenrecord。</p> |
| |
| <p>然后,screenrecord 命令要求 SurfaceFlinger 创建一个镜像主显示设备的虚拟显示设备(即,它具有完全相同的层),并指示它将输出发送到来自 mediaserver 的 Surface。在这种情况下,SurfaceFlinger 是缓冲区的生产方,而不是消耗方。</p> |
| |
| <p>配置完成后,screenrecord 会等待显示编码数据。在应用绘制时,其缓冲区会前往 SurfaceFlinger,SurfaceFlinger 将它们合成为单个缓冲区,然后直接发送到 mediaserver 中的视频编码器。screenrecord 进程甚至从未看到过完整的帧。在内部,mediaserver 具有自己的移动缓冲区的方式,这种方式还通过句柄传递数据,从而最大限度地降低开销。</p> |
| |
| <h2 id="simulate-secondary">案例研究:模拟辅助显示设备</h2> |
| |
| <p>WindowManager 可以要求 SurfaceFlinger 创建一个可见层,将 SurfaceFlinger 作为其 BufferQueue 消耗方。也可以要求 SurfaceFlinger 创建一个虚拟显示设备,同样以 SurfaceFlinger 作为其 BufferQueue 生产方。如果连接它们并配置渲染到可见层的虚拟显示设备,会出现什么情况?</p> |
| |
| <p>创建一个闭合循环,其中合成的屏幕显示在窗口中。该窗口现在是合成输出的一部分,因此在下一次刷新时,该窗口中的合成图像也会显示窗口内容(然后<a href="https://en.wikipedia.org/wiki/Turtles_all_the_way_down">一直循环下去</a>)。要实际查看该过程,请在设置中启用<a href="http://developer.android.com/tools/index.html">开发者选项</a>,选择<strong>模拟辅助显示设备</strong>,然后启用一个窗口。另外有个好处是,可使用 screenrecord 捕获启用显示设备的操作,然后逐帧播放。</p> |
| |
| </body></html> |