1. Chromium 的渲染流水线

Blink —> Paint -> Commit -> (Tiling ->) Raster -> Activate -> Draw(Submit) —> Viz

Blink 对接 cc 的绘制接口进行 Paint,Paint 生成 cc 模块的数据源(cc::Layer),CC 将数据源进行合成,经过一系列过程最终在 Draw 阶段将合成的结果(viz::CompositorFrame)提交到 Viz。 也就是说,Blink 负责网页内容的绘制,cc 负责将绘制的结果进行合成并提交到 Viz

Blink 负责浏览器网页部分的绘制,//ui/views 模块负责浏览器非网页部分 UI 的绘制,它们的绘制都对接 cc。如果我们扩展 //ui/views 的能力,就可以在 cc 之上建立一套新的 UI 框架,比如我们把这套新的 UI 框架叫做 Flutter。真正的 Flutter 当然没有这么简单,但其实 Flutter 最初是由 Chromium 的开发人员建立的(信息来自网络,未求证),所以 Flutter 渲染流水线或多或少的会吸取 Chromium 渲染的经验。最近 UC 内核团队开始投入 Flutter 的研究也算他们团队的一个新方向了。

2. cc 的架构设计

cc 的设计相对简单,它不直接涉及跨进程操作(它依赖的 SharedImage 以及 Viz 负责夸进程处理),可以理解为一个简单的多线程异步流水线。运行在 Browser 进程中的 cc 负责合成浏览器非网页部分的 UI,运行在 Renderer 进程中的 cc 负责网页的合成。

在 How cc Works 中给出的下图能很好的反应 cc 的核心逻辑:

Chromium CC渲染层工作流详解-LMLPHP

cc 的多线程体现在不同阶段运行在不同的线程中,Paint 运行在 Main 线程,CommitActivateSubmit 运行在 Compositor 线程,而 Raster 运行在专门的 Raster 线程。

不同的 cc client 有自己的 paint 方式,比如 views 和 blink 都是 cc 的 client,他们有各自不同的 paint 逻辑,但是其他比如 commit,raster 等都是一样的逻辑,因此如果要学习 cc 可以从 views 入手,不必一开始就深入到 blink 中。

下面是基于这个 demo 的 cc 类图:

Chromium CC渲染层工作流详解-LMLPHP

图中蓝色表示核心数据结构,用于存储数据,橙色表示核心类/接口,用于执行核心逻辑。

3. Paint

Paint 用于产生 cc 的数据源,生成 cc::Layer 树。一个 cc::Layer 表示一个矩形区域内的 UI。它有很多子类,用于存储不同类型的 UI 数据,下面简单介绍一下:

  • cc::PictureLayer 用于实现自绘型的 UI 组件,比如上层的各种 Button,Label 等都可以用它来实现。它允许外部通过实现 cc::ContentLayerClient 接口提供一个 cc::DisplayItemList 对象,它表示一个绘制操作的列表,记录了一系列的绘制操作,比如画线,画矩形,画圆等。通过 cc::PaintCanvas 接口可以方便的创建复杂的自绘 UI。cc::PictureLayer 还是唯一需要 Raster 的 cc::Layer,关于 Raster 见下文。它经过 cc 的流水线之后转换为一个或多个 viz::TileDrawQuad 存储在 viz::CompositorFrame 中。
  • cc::TextureLayer 对应 viz 中的 viz::TextureDrawQuad,所有想要使用自己的逻辑进行 Raster 的 UI 组件都可以使用这种 Layer,比如 Flash 插件,WebGL等。
  • cc::SurfaceLayer 对应 viz 中的 viz::SurfaceDrawQuad,用于嵌入其他的 CompositorFrame。Blink 中的 iframe 和视频播放器可以使用这种 Layer 实现。
  • cc::UIResourceLayer/cc::NinePatchLayer 类似 TextureLayer,用于软件渲染。
  • cc::SolidColorLayer 用于显示纯色的 UI 组件。
  • cc::VideoLayer 以前用于专门显示视频,被 SurfaceLayer 取代。

Blink 和 //ui/views 等上层 UI 绘制引擎通过以上各种 cc::Layer 来描述 UI 并实现和 cc 的对接。由于 cc::Layer 本身可以保存 Child cc::Layer,因此给定一个 Layer 对象,它实际上表示一棵 cc::Layer 树,这个 Layer 树即主线程 Layer 树,因为它运行在主线程中,并且主线程有且只有一棵 cc::Layer 树。

下面是执行 Paint 的堆栈,这里是调用 //ui/views 组件的堆栈,如果是 Blink 则 aura 和 views 命名空间下的堆栈会换成 blink。

Chromium CC渲染层工作流详解-LMLPHP

 blink 的如下:

Chromium CC渲染层工作流详解-LMLPHP

4. Commit

Commit 阶段的核心作用是将保存在 cc::Layer 中的数据提交(PushPropertiesTo)到 cc::LayerImpl 中。cc::LayerImpl 和 cc::Layer 一一对应,只不过运行在 Compositor 线程中(也称为 Impl 线程)。在 Commit 完成之后会根据需要创建 Tiles 任务,这些任务被 Post 到 Raster 线程中执行。

下面是 Commit 部分的堆栈:

Chromium CC渲染层工作流详解-LMLPHP

 5. Tiling+Raster

在 Commit 阶段创建的 Tiles 任务(cc::RasterTaskImpl)在该阶段被执行。Tiling 阶段最重要的作用是将一个 cc::PictureLayerImpl 根据不同的 scale 级别,不同的大小拆分为多个 cc::TileTask 任务。Raster 阶段会执行每一个 TileTask,将 DisplayItemList 中的绘制操作 Playback 到 viz 的资源中。 由于 Raster 比较耗时,属于渲染的性能敏感路径,因此Chromium在这里实现了多种策略以适应不同的情况。这些策略主要在两方面进行优化,一方面是 Raster 结果(也就是资源)存储的位置,一方面是 Raster 中 Playback 的方式。这些方案被封装在了 cc::RasterBufferProvider 的子类中,下面一一进行介绍:

  • cc::GpuRasterBufferProvider 使用 GPU 进行 Raster,Raster 的结果直接存储在 SharedImage 中(可以理解为 GLTexture,详细信息见 GPU SharedImage)。
  • cc::OneCopyRasterBufferProvider 使用 Skia 进行 Raster,结果先保存到 GpuMemoryBuffer 中,然后再将 GpuMemoryBuffer 中的数据通过 CopySubTexture 拷贝到资源的 SharedImage 中。GpuMemeoryBuffer 在不同平台有不同的实现,也并不是所有的平台都支持,在 Linux 平台上底层实现为 Native Pixmap(来自X11中的概念),在 Windows 平台上底层实现为 DXGI,在 Android 上底层实现为 AndroidHardwareBuffer,在 Mac 上底层实现为 IOSurface。
  • cc::ZeroCopyRasterBufferProvider 使用 Skia 进行 Raster,结果保存到 GpuMemoryBuffer 中,然后使用 GpuMemoryBuffer 直接创建 SharedImage。
  • cc::BitmapRasterBufferProvider 使用 Skia 进行 Raster,结果保存到共享内存中。

Raster 最终会产生一个资源,该资源被(间接)记录在了 cc::PictureLayerImpl 中,他们会在最终的 Draw 阶段被放入 CompositorFrame 中。

除了 cc::PictureLayer 的 Raster 之外,在该阶段还会进行图片的解码,关于这部分的详细信息以后如果有时间再补充。

TODO: 补充 GpuMemoryBuffer 和 图片解码相关内容。

下面是抓取到的 cc::RasterTaskImpl 的执行流程:

Chromium CC渲染层工作流详解-LMLPHP

 6. Activate

在 Impl 端有三个 cc::LayerImpl 树,分别是 Pending,Active,Recycle 树。Commit 阶段提交的目标其实就是 Pending 树,Raster 的结果也被存储在了 Pending 树中。

在 Activate 阶段,Pending 树中的所有 cc::LayerImpl 会被复制到 Active 树中,为了避免频繁的创建 cc::LayerImpl 对象,此时 Pending 树并不会被销毁,而是退化为 Recycle 树。

和主线程 cc::Layer 树不同,cc::LayerImpl 树并不是自己维护树形结构的,而是由 cc::LayerTreeImpl 对象来维护 cc::LayerImpl 树的。三个 Impl 树分别对应三个 cc::LayerTreeImpl 对象。

下面是 Activate 阶段的执行堆栈:

Chromium CC渲染层工作流详解-LMLPHP

 7. Draw

Draw 阶段并不执行真正的绘制,而是遍历 Active 树中的 cc::LayerImpl 对象,并调用它的 cc::LayerImpl::AppendQuads 方法创建合适的 viz::DrawQuad 放入 CompositorFrame 的 RenderPass 中。cc::LayerImpl 中的资源会被创建为 viz::TransferabelResource 存入 CompositorFrame 的资源列表中。至此一个 viz::CompositorFrame 对象创建完毕,最后通过 cc::LayerTreeFrameSink 接口将该 CompositorFrame 发送到给 viz 进程(GPU进程)进行渲染。

下面是 Draw 阶段的运行堆栈:

Chromium CC渲染层工作流详解-LMLPHP

 8. Scheduler 调度器

上面已经介绍了 cc 渲染流水线的各个过程,这些过程是由 cc::Scheduler 进行调度的。它控制 cc 在合适的时机执行合适的动作,内部维护了一个渲染的状态机, 其核心调度逻辑在 cc::Scheduler::ProcessScheduledActions() 中,代码如下:

Chromium CC渲染层工作流详解-LMLPHP

 9. 参考文献

Chromium CC渲染层工作流详解-LMLPHP

10-30 18:47