blob: 79d34dbf3f1fe2e1ad0caa849837ce5941b5635b [file] [log] [blame]
<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>