Chromium Viz 浅析 - SkiaRenderer & SkiaOutputSurface

关于 Viz 的系列文章

Chromium Viz 浅析 - 介绍篇
Chromium Viz 浅析 - 合成器架构篇


Chromium 关于光栅化和合成的一些主要性能优化项目包括 OOPD (Out of Process Display Compositor,进程外 Display 合成器), OOPR (Out of Process Rasterization,进程外光栅化) 和 SkiaRenderer。

我们在Chromium Viz 浅析 - 合成器架构篇介绍过 OOPD,它的主要目的是将 Display Compositor 从 Browser 进程迁移到 Viz 进程(也就是原来的 GPU 进程),Browser 则变成了 Viz 的一个 Client,Renderer 跟 Viz 建立链接(CompositorFrameSink)虽然还是要通过 Browser,但是建立链接后提交 CompositorFrame 就是直接提交给 Viz 了。Browser 同样也是提交 CompositorFrame 给 Viz,最后在 Viz 生成最终的 CompositorFrame,通过 Display 交由 Renderer 合成输出。

How cc Works 中文译文一文中,我们简单提及过 OOPR。OOPR 跟目前的 GPU 光栅化机制的主要区别是:

  1. 在当前的 GPU 光栅化机制中,Worker 线程在执行光栅化任务时,会调用 Skia 将 2D 绘图指令转换成 GPU 指令,Skia 发出的 GPU 指令通过 Command Buffer 传送到 Viz 进程的 GPU 线程中执行;
  2. 在 OOPR 中,Worker 线程在执行光栅化任务时,它直接将 2D 绘图指令(DisplayItemList)序列化到 Command Buffer 传送到 GPU 线程,运行在 GPU 线程的部分再调用 Skia 去生成对应的 GPU 指令,并交由 GPU 直接执行;

简而言之,就是将 Skia 光栅化的部分从 Renderer 进程转移到了 Viz 进程。

本文接下来的部分我们会对 SkiaRenderer 和 SkiaOutputSurface 进行分析。

DirectRenderer & OutputSurface

在继续分析 SkiaRenderer 和 SkiaOutputSurface 之前,我们先简单介绍一下它们的基类 DirectRendererOutputSurface

顾名思义,DirectRenderer 是 Display 用来合成输出最终的 CompositorFrame,它遍历 CompositorFrame 中的每一个 RenderPass,遍历每个 RenderPass 中的每个 DrawQuad,为每个 DrawQuad 生成相应的绘制指令。而 OutputSurface 则用来为最终的合成输出提供一个目标 Surface,对 Renderer 来说,它的主要作用是为 Renderer 提供一个绘图上下文。

在 SkiaRenderer 之前,我们在 GPU 合成时,使用的 Renderer 是 GLRenderer,顾名思义,它为每个 DrawQuad 生成的绘制指令是 GL 指令,它需要 OutputSurface 提供一个 ContextProvider 作为绘图上下文,通过 ContextProvider GLRenderer 可以获取一个基于 Command Buffer 的 GL 接口,然后通过该 GL 接口发出 GL 指令。

历史上还曾经有过 DelegatingRenderer,跟 DirectRenderer 对应,但是新的合成器架构应用后,DelegatingRenderer 已经不再需要了,所以现在只有 DirectRenderer 和它的派生类。

SkiaRenderer & SkiaOutputSurface

当我们开启 OOPD 后,Display Compositor 就运行在 Viz 进程的 Viz Compositor 线程,这意味则它跟 GPU 线程是同一个进程,理论上就不再需要使用 Command Buffer,虽然目前的 GLRenderer 还是需要使用一个 InProcessCommandBuffer 来做跨线程的 GL 指令调用。而 SkiaRenderer 则让我们可以完全避免在合成过程中使用 Command Buffer。

跟 GLRenderer 对应,SkiaRenderer 为每个 DrawQuad 生成的绘制指令是一个 Skia 的绘制指令,通过 SkiaOutputSurface 提供的 SkCanvas 输出。SkiaOutputSurface 有两个派生类 SkiaOutputSurfaceImpl 和 SkiaOutputSurfaceImplNonDDL,SkiaOutputSurfaceImplNonDDL 应该只是一个 Android WebView 的特殊合成器架构在重构过程中使用的一个过渡实现,所以我们重点讲解 SkiaOutputSurfaceImpl。

之前我们讲过 Display Compositor 运行在 Viz Compositor 线程,由它来调用 SkiaRenderer,但是 Skia 输出的 GPU 指令必须在 GPU 线程才能执行。所以实际上 SkiaOutputSurfaceImpl 给 SkiaRenderer 提供的 SkCanvas,它的目标 SkSurface 并不是真正用于当前输出的 SkSurface,而是一个 Compatible SkSurface。SkiaOutputSurfaceImpl 使用最终 SkSurface 的特征信息生成 SkDeferredDisplayListRecorder,然后获取 SkDeferredDisplayListRecorder 提供的 SkCanvas,SkiaRenderer 通过该 SkCanvas 输出的绘制指令实际上被存储到 Recorder 内部的 SkDeferredDisplayList 中。

当 SkiaRenderer 绘制完一个 RenderPass 后,SkiaOutputSurfaceImpl 从 SkDeferredDisplayListRecorder 剥离对应的 SkDeferredDisplayList,然后通过 Post GPU Task 的方式传递给在 GPU 线程等待的 SkiaOutputSurfaceImplOnGpu,SkiaOutputSurfaceImplOnGpu 再将这个 SkDeferredDisplayList playback 到真正输出的 SkSurface 上。

Promise SkImage

Promise SkImage 是一个蛮有意思的概念,它的引入主要是因为 SkiaRenderer 运行在 Viz Compositor 线程,并没有 GPU 上下文,所以当它需要绘制一个图片时,此时无法访问该图片对应的 GPU 资源,但是实际上该图片又是一个 GPU 资源的引用。不过因为 SkiaRenderer 是先绘制到 SkDeferredDisplayList,然后 SkDeferredDisplayList 再在 GPU 线程被 playback,所以引入了 Promise SkImage 来解决这个问题。

Promise SkImage 实际上是一个 placeholder,它包含了一些用于定位对应 GPU 资源的 meta 信息和一组回调函数的指针,当 SkDeferredDisplayList 被 playback 过程中,当需要绘制该 Promise SkImage 时,就会调用这些回调函数进行 Fulfill,Fulfill 回调函数会返回一个包含对应的 GPU 资源信息 SkPromiseImageTexture 供 Skia 使用。

所以 Promise SkImage 实际上就是 - "Make a promise, and fulfill in future"。

Next BIG Thing

仅仅是用 SkiaRenderer 替换原来的 GLRenderer,其实并没有马上带来什么性能上的优势。诚然,SkiaRenderer 不需要使用 Command Buffer,省去了 Encode/Decode 的开销,但是 Skia 本身的开销也不小,两相比较,也很难说有多少性能提升。所以 SkiaRenderer 更多是跟 OOPR 一起,为后续的进一步渲染性能优化打下基础。

我们可以看到,当 OOPD,OOPR 和 SkiaRenderer 都开启后:

  1. 光栅化和合成都迁到了 Viz 进程;
  2. 光栅化和合成都使用 Skia 做 2D 绘制,实际上 Chromium 所有的 2D 绘制最终都交由 Skia 来做,由 Skia 生成对应的 GPU 指令;
  3. 光栅化和合成时,Skia 最终输出 GPU 指令都在 GPU 线程,并且使用同一个 Skia GrContext(Skia 内部的 GPU 绘图上下文);

这意味着,当 Skia 对 Vulkan,Metal,DX12 等其它 3D API 的支持完善后,Chromium 就可以根据不同的平台和设备,来决定 Skia 使用哪个 GPU API 来做光栅化和合成。而 Vulkan,Metal,DX12 这些更 Low Level 的 3D API,对比 GL API,可以带来更低的 CPU 开销和更好的性能。

上一篇:MotionMark 浏览器图形性能测试套件深入分析


下一篇:渲染流水线中的光栅化