Chromium Viz 浅析 - 合成器架构篇

简介: **关于 Viz 的系列文章** [Chromium Viz 浅析 - 介绍篇][1] ---- ## Mojo 快速入门 因为后面的内容需要读者对 Mojo 有一定了解,如果以前没接触过的话,这部分内容提供了一个快速的入门。 简单的说 Mojo 是一个 Client/Service 的通讯机制,支持跨进程。一个 Mojo 链接建立的过程一般如下: 1. Cli

关于 Viz 的系列文章

Chromium Viz 浅析 - 介绍篇


Mojo 快速入门

因为后面的内容需要读者对 Mojo 有一定了解,如果以前没接触过的话,这部分内容提供了一个快速的入门。

简单的说 Mojo 是一个 Client/Service 的通讯机制,支持跨进程。一个 Mojo 链接建立的过程一般如下:

  1. Client 端创建一个 Mojo 接口对象;
  2. Client 端为该接口对象生成一个链接请求对象;
  3. Client 端将该链接请求对象发送给 Service 端(一般是通过另外一个已经建立链接的 Mojo 接口对象);
  4. Service 端接收到链接请求对象后,会跟一个对应 Mojo 接口的实现对象进行绑定,这样 Client/Service 的链接就建立起来了;
  5. Client 端通过上面的 Mojo 接口对象给 Service 端发送消息(通过接口的方法调用,可以在链接还没建立之前就发送);
  6. Service 端接收到该消息,会转交给之前创建的 Mojo 接口的实现对象处理(调用实现对象的对应方法);
  7. 如果该消息需要返回值,会将返回值回传给 Client 端;

如果是 Client 端需要接收 Service 端的回调,建立链接的过程则有所不同:

  1. Client 端创建一个 Mojo 接口对象;
  2. Client 端为该接口对象生成一个链接请求对象;
  3. Client 端将该链接请求对象跟一个对应 Mojo 接口的实现对象进行绑定;
  4. Client 端将上述的 Mojo 接口对象发送给 Service 端(一般是通过另外一个已经建立链接的 Mojo 接口对象);
  5. Service 端收到 Mojo 接口对象后,则可以使用该接口对象给 Client 端发送消息;

更多关于 Mojo 的内容可以阅读官方的文档 Intro to Mojo & Services

合成器架构

当我们考察 Chromium 的合成器架构的时候,总是会有一些疑问:

  1. 为什么 Chromium 需要在 Layer Compositor 之上再构建一个 Display Compositor,并且还将 Display Compositor 包装成 Viz Service 服务的一部分,为什么 Layer Compositor 不能自己使用一个 Renderer 直出?
  2. Surface 跟 Layer 有什么不同?

我尝试根据自己的理解来回答上述的问题:

Surface 的确某种程度跟 Layer 有相似之处,但是 Surface 提供了一个更简单抽象的概念(CompositorFrame Queue),更容易用于结构简单的非网页内容的合成输出,比如浏览器 UI,插件等。

Surface 的父子关系并不是预先确定的,实际上 Surface 之间是相互独立的,它们的父子关系是由某个 Surface 当前的 CompositorFrame 包含一个 SurfaceDrawQuad 指向另外一个 Surface 形成的嵌套关系来决定的,默认 Display Compositor 会主动创建一个 Surface 作为 Root Surface 来 Attach 外部来源的 Surface。这种方式比较 Layer 需要事先确定树结构来说更为灵活,当然这也是因为 Surface 之间不会有太复杂的嵌套关系。

以 Surface/CompositorFrame 为基础构建的 Display Compositor,并且被包装成 Viz Service 服务的一部分,使得一棵 Surface 树是可以支持跨进程的(父子 Surface 来源不同的进程),这对于 OOPIF(进程外 iframe)和插件来说是必须的。

部分网页元素如 Video,VR/AR,Offscreen Canvas 理论上也可以使用 Layer,但是使用独立的 Surface,让 Layer 作为 Surface 的 placeholder,可以使得它们的后续更新能够跳过 Layer Compositor 冗长的处理步骤,直接通过 Display Composior 输出。从而使得这些元素本身的更新更高效,输出延迟更低,也更省电。这也是因为它们的内容来源不是网页本身,不需要通过 Layer Compositor 光栅化,并且不需要跟其它 DOM 元素同时更新。

但是反过来又有另外的问题了,假设我们只需要合成输出一个比较普通的网页,不需要绘制浏览器 UI,没有 Video,VR/AR,OffscreenCanvas 元素或者它们不使用 Surface,没有或者不打算支持进程外 iframe 和插件,目前 Chromium 复杂的合成器架构是不是反而增加了很多额外的开销,导致可能的性能下降和耗电?

我个人觉得这个问题的答案,很不幸地会是 Yes,跨进程的 IPC,复杂的 Display Composior 架构的确会带来一些额外的开销,对普通的网页来说,即使线程并发能够抵消一部分性能损失,但是它的绘制输出延迟和耗电还是会增加,有时鱼和熊掌就是不可得兼。

下面的内容会继续介绍 Chromium 目前在 Android 平台的合成器架构,在普通网页上的三种不同的实际应用方案。

Android WebView 的合成器架构

Android WebView 的合成器架构跟 Chromium 其它的 Configurations 有很大的差异,造成这种现象的原因主要是:

  1. WebView 没有自己独立的 Window 作为 Display 的 OutputSurface,而是需要绘制到 WebView 所在 Activity 的 Window;
  2. WebView 的 Display Compositor 运行在 Android Render 线程,也就是 Android UI 的 GPU 线程,它不能自己控制合成输出的时机,只能等待 Android UI 的回调;
  3. Android Render 线程没有消息循环(或者说没有对外暴露),WebView 无法控制该线程的消息处理,只能通过在 UI 线程 Invalidate WebView 然后等待 Android Render 线程回调的方式来获得合成输出的机会;

在 Android WebView,实际上是没有 Viz Service 的,WebView 自己做了一套特殊的适配机制 SynchronousCompositor,将 Layer Compositor 和 Display Compositor 联系起来,而没有通过 Viz Service 服务。

在 Android WebView 合成器架构中:

对接 Layer Compositor 的 LayerTreeFrameSink 的实现是 SynchronousLayerTreeFrameSink。Layer Compositor 输出 CompositorFrame 并不是 cc 自己主动 Push,而是等待 SynchronousCompositor 的 Host 端向 Proxy 端发起请求。Host 端在 Browser 的 UI 线程(部分接收回传消息的对象在 IO 线程),Proxy 在 Renderer 的 Compositor 线程,之间通过 Mojo 通讯。Host 端在 WebView.onDraw 被调用的过程中,请求 Proxy 输出 CompositorFrame,Proxy 通过 SynchronousLayerTreeFrameSink 的回调获得 CompositorFrame 并返回给 Host,这是一种 Pull 的模型,区别于其它 Configurations 使用的 Push 模型。

SurfacesInstance 包含了一个 Display Compositor 的实例,同时也包括一个 FrameSinkManager 的实现作为单机版的 Viz Service,这个 Display Compositor 的实例供所有 WebView 共享,它的 OutputSurface 实际上只是当前 GL Context Window Surface 的 Wrapper。

HardwareRenderer 会通过 SurfacesInstance 申请一个 Surface,它被一个 CompositorFrameSinkSupport 对象所持有,跟所属 WebView 对应。当 HardwareRenderer 被 Android Render 线程回调获得合成输出的机会时,会从 SynchronousCompositor 的 Host 端获取 CompositorFrame,然后置入申请的 Surface,再请求 SurfacesInstance 的 Display Compositor 将该 Surface Attch 到 Root Surface 上,最后合成输出。

因为缺少完整的 Viz Service 服务,也使得 Android WebView 当前的合成输出功能有较大的局限性,每个 WebView 现在只有一个对应 Surface 对应网页本身,不支持 OOPIF,插件,独立 Surface 的 Video 等,当然 WebView 也不需要自己来绘制 UI。

理论上 WebView 应该也可以支持完整的合成输出功能,主 Surface 继续沿用目前 Pull 的模型,次级 Surface 可以使用标准的 Push 模型。不过目前来说还没有更多的信息。

非 OOP-D 的合成器架构

对于作为独立浏览器的 Chrome for Android 来说,它的合成器架构跟 Android WebView 的特殊用法差异比较大,这才是 Viz 的标准用法。OOP-D(进程外 Display Compositor)是新的合成器架构,在 M75 版本已经默认开启,不过在这里我们还是先介绍一下非 OOP-D 的合成器架构。

无论是否使用 OOP-D,当 Renderer 创建 Layer Compositor 的时候,它都是创建一个 AsyncLayerTreeFrameSink 作为 CompositorFrame 的提交目标对象。AsyncLayerTreeFrameSink 在 Compositor 线程被初始化,同时建立了 CompositorFrameSink 接口跟 Viz Service 端的链接,后续会使用该接口向 Display Compositor 提交 CompositorFrame。

Browser 端的合成器位于 CompositorImplCompositorImpl 内部有一个 CompositorDependencies 单例,它包括了一个 HostFrameSinkManager 作为 FrameSinkManager 接口的 Wrapper。没有开启 OOP-D 时,CompositorDependencies 会直接创建一个 FrameSinkManagerImpl 对象作为 HostFrameSinkManager 的 Local Manager,在本地直接处理 Renderer 发过来的 CompositorFrameSink 接口的链接请求。

同时 CompositorImpl 还会在本地创建一个 Display Compositor,通过上面的 FrameSinkManagerImpl 来创建和访问 Surface,当 Renderer 的 Layer Compositor 提交新的 CompositorFrame 的时候,就会触发该 Display Compositor 的合成输出。

None OOPD

为了调试方便,截图使用的 Chromium 都运行在单进程模式

上图显示了页面被拖动时绘制一帧的流程,从 Browser UI 线程收到 VSync 开始,Layer Compositor 收到 BeginFrame 信号后产生新的 CompositorFrame 发送给位于 Browser UI 线程的 Display Compositor 合成输出,合成输出的 GL 指令在 GPU 线程被执行。

None OOPD

非 OOP-D 的合成器架构,绘图来源自 Compositor Stack

参考上图,实际的合成器架构比上面的描述要复杂,在 Chrome for Android,Browser 和 Renderer 都会各创建一个 Layer Compositor,Browser 创建的 Layer Compositor 会创建一个 SurfaceLayer 作为 Renderer Layer Compositor 对应 Surface 的 placeholder,Browser Layer Compositor 同时还可以 Attach 其它 UILayer 用来绘制浏览器 UI,当 Display Compositor 聚合 Surface 时,Browser Layer Compositor 对应的 Surface 会嵌套 Renderer Layer Compositor 对应的 Surface,网页和浏览器 UI 最终被聚合成一个完整的 CompositorFrame 一起合成输出。

OOP-D 的合成器架构

OOPD

OOP-D 的合成器架构,绘图来源自 Compositor Stack

对于 OOP-D 的合成器架构,CompositorDependencies 会发送一个 FrameSinkManager 接口的链接请求给在 Viz 进程 VizCompositor 线程的 Viz Main Service,然后把该 FrameSinkManager 接口传递给 HostFrameSinkManager。当 HostFrameSinkManager 接收到 Renderer 端对 CompositorFrameSink 接口的链接请求时,它会通过 FrameSinkManager 接口转交给远端的 Viz Service 处理。

void HostFrameSinkManager::CreateCompositorFrameSink(
    const FrameSinkId& frame_sink_id,
    mojom::CompositorFrameSinkRequest request,
    mojom::CompositorFrameSinkClientPtr client) {
  ...

  frame_sink_manager_->CreateCompositorFrameSink(
      frame_sink_id, std::move(request), std::move(client));
}

frame_sink_manager_ 在开启 OOP-D 时,是一个 Mojo 接口对象,如果没有开启 OOP-D,则指向一个 Local FrameSinkManagerImpl 对象

CompositorImpl 同样需要为 Browser Layer Compositor 建立一个 CompositorFrameSink 接口的链接,它是一个 Root CompositorFrameSink,远端的 Viz Service 在建立该 CompositorFrameSink 接口链接的同时,也创建了一个 Display Compositor 用于合成输出。

OOPD

为了调试方便,截图使用的 Chromium 都运行在单进程模式

从上图我们可以看到跟非 OOP-D 相比,除了 VSync 的触发,Display Compositor 的合成输出都迁移到了 Viz 进程的 VizCompositor 线程外,其它并没有太大的差异。不过 Display Compositor 的迁移仅仅是 OOP-D 的第一阶段,真正的差异会在后续的改动中体现,包括:

  1. Display Compositor 移除 Command Buffer 的使用,因为 VizCompositor 线程和 GPU 线程已经同在一个进程,它们之间可以使用线程通讯而不需要使用 Command Buffer;
  2. Display Compositor 移除 Command Buffer 后,可以使用 Vulkan 来取代 GL,因为 Command Buffer 不支持 Vulkan 也不打算支持,所以 1 是 2 的前置条件;
目录
相关文章
|
Web App开发 域名解析 缓存
如何在 Ubuntu 20.04 上安装 Node.js 和 npm
本文我们主要为大家介绍在 Ubuntu 20.04 上安装 Node.js 和 npm 的三种不同的方式。
165017 7
如何在 Ubuntu 20.04 上安装 Node.js 和 npm
|
3月前
|
人工智能 安全 API
拼多多:通过拼团API发起社交裂变,低成本获取新客
拼多多通过拼团API实现社交裂变,以低至0.5元/人的成本高效获客。其核心在于:拼团模式激发用户分享,API技术保障流畅体验,裂变系数F>1.5时形成指数传播。数学模型显示,新客呈指数增长,获客成本趋近于零。该模式为电商低成本扩张提供范本。(238字)
394 0
|
算法 测试技术 AI芯片
CPU反超NPU,llama.cpp生成速度翻5倍!LLM端侧部署新范式T-MAC开源
【9月更文挑战第7天】微软研究院提出了一种名为T-MAC的创新方法,旨在解决大型语言模型在资源受限的边缘设备上高效部署的问题。T-MAC通过查表法在CPU上实现低比特LLM的高效推理,支持混合精度矩阵乘法,无需解量化。其通过位级查表实现统一且可扩展的解决方案,优化数据布局和重用率,显著提升了单线程和多线程下的mpGEMV及mpGEMM性能,并在端到端推理吞吐量和能效方面表现出色。然而,表量化和快速聚合技术可能引入近似和数值误差,影响模型准确性。论文详见:[链接](https://www.arxiv.org/pdf/2407.00088)。
746 10
|
人工智能 编解码 5G
虚拟现实(VR)与增强现实(AR)的融合:开启全新交互时代
【6月更文挑战第17天】虚拟现实(VR)与增强现实(AR)融合成混合现实(MR),打造全新交互体验。MR结合VR的沉浸感和AR的现实增强,应用于教育、游戏、设计和营销,带来创新教学方式、沉浸式游戏体验和高效设计工具。尽管面临技术挑战,随着5G和AI的发展,MR有望引领未来交互的革命。
|
Java 开发者
Java一分钟之-JavaFX布局管理:GridPane, VBox, HBox
本文介绍了JavaFX的三种常用布局管理器:GridPane、VBox和HBox。GridPane用于创建二维网格布局,需设置行和列约束以防止控件重叠。VBox按垂直方向堆叠控件,记得设置间距。HBox水平排列控件,可能需要分配额外空间以避免水平滚动条。示例代码展示了这三种布局的使用。理解并运用这些布局管理器能提升JavaFX应用的界面设计。
671 0
|
缓存 Android开发
Skia深入分析5——skia文字绘制的实现
文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。 1、SkPaint 文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关
7503 0
|
JavaScript 前端开发 NoSQL
技术心得:微信小助手之wechaty模块(个人笔记,于人无用)
技术心得:微信小助手之wechaty模块(个人笔记,于人无用)
207 0
|
编解码 边缘计算 vr&ar
注视点渲染Foveated rendering是什么
Foveated rendering听起来好像是非常复杂的技术。但实际上注视点(foveation)的底层概念非常直接。使用人注视屏幕的信息,可以减少生成场景的运算资源,方式是使用高分辨率渲染人眼观看的小区哉,而场景外的其它区域(人的四周)采用更小的分辨率和更少的细节。注视点渲染主要用于显示技术,如VR头显和AR眼镜,对些场景资源优化至关重要。
681 1
|
存储 缓存 JavaScript
浏览器渲染机制(二)下
浏览器渲染机制(二)下
655 0
|
前端开发 JavaScript
前端——JavaScript获取系统时间以及通过ajax获取服务器时间
前端——JavaScript获取系统时间以及通过ajax获取服务器时间