前言
此文章是我最近在看的【WebKit 技术内幕】一书的一些理解和做的笔记。
而【WebKit 技术内幕】是基于 WebKit 的 Chromium 项目的讲解。
书接上文 浏览器内核之渲染基础
1. 硬件加速基础
1.1 概念
硬件加速技术是指:使用 GPU 的硬件能力为帮助渲染网页,在为 GPU 的作用主要是用来绘制 3D 图形并且性能特别好。
对于 GPU 绘图而言,当网页分层之后,部分区域的更新可能只在网页的一层或者几层,而不需要将整个网页都重新绘制。 通过重新绘制网页的一个或者几个层,并将它们和其他之前绘制完的层合成起来,既能使用 GPU 的能力,又能减少重绘的开销。
每个 RenderLayer 对象都有一个后端存储与其对应,好处是:当每一层更新的时候,WebKit 只需要更新 RenderLayer 对象包含的节点即可。所以当某一层有作保更新的时候,WebKit 重绘该层的所在内容, 这是理想的情况。现实中,由于硬件能力和资源有限。为了节省 GPU 的内存资源,硬件加速机制在 RenderLayer 树建立之后需要做三件事情来完成网页的渲染。
- WebKit 决定将哪些 RenderLayer 对象组合在一起,形成一个有后端存储的新层,这一新层不久后会用于之后的合成(Compositing),这里面称之为合成层(Compositing Layer)。每个新层都有一个或者多个后端存储,这里的后端存储可能是 GPU 的内存。对于一个 RenderLayer 对象,如果它没有后端存储的新层,那么就使用它的父亲所使用的合成层。
- 将每个合成层包含的这些 RenderLayer 内容绘制在合成层的后端存储中,这里的绘制可以是软件绘制也可以是硬件绘制。
- 由合成层(Compositor)将多个合成层合成起来,形成网页的最终可视化结果,实际就是一张图片。 合成器是一种能够将多个合成层按照这些层的前后顺序、合成层的 3D 变形等设置而合成一个图像结果的设施。
在 WebKit 中,只有把编译的 C 代码宏(macro)“ACCELERATED_COMPOSITING” 打开之后,硬件加速机制才会被开启,有关硬件加速的基础设施才会被编译进去。
1.2 WebKit 硬件加速设施
一个 RenderLayer 对象如果需要后端存储,它会创建一个 RenderLayerBacking 对象,该对象负责 RenderLayer 对象所需要的各种存储。理想情况下,每个 RenderLayer 都可以创建自己的后端存储,但事实上不是所有 RenderLayer 都有自己的 RenderLayerBacking 对象。如果一个 RenderLayer 对象被 WebKit 按照一定的规则创建了后端存储,那么该 RenderLayer 被称为合成层。
每个合成层都有一个 RenderLayerBacking, RenderLayerBacking 负责管理 RenderLayer 所需要的所有后端存储,因为后端存储可能需要多个存储空间。在 WebKit 中,存储空间使用 GraphicsLayer 类来表示。
其中图中的 GraphicsLayer 表示 RenderLayer 中前景层、背景层所需要的一个后端存储。每一个 GraphicsLayer 都使用一个 GraphicsLayerClient 对象,该对象能够收到 GraphicsLayer 的一些状态更新信息,并且包含一个绘制该 GraphicsLayer 对象的方法,RenderLayerBacking 继承于该类。GraphicsLayer 是 WebKit 中的基础类,主要定义一套标准接口。
如果一个 RenderLayer 对象具有以下特征之一,那么它就是合成层。
- RenderLayer 具有 CSS 3D 属性或者 CSS 透视效果。
- RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的 HTML5 “video” 元素。
- RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的 Canvas 2D 元素或者 WebGL 技术。
- RenderLayer 使用了 CSS 透明效果的动画或者 CSS 变换的动画。
- RenderLayer 使用了硬件加速的 CSS Filters 技术。
- RenderLayer 使用了剪裁(Clip)或者反射(Reflection)属性,并且它的后代中包括一个合成层。
- RenderLayer 有一个 Z 坐标比自己小的兄弟节点,且该节点是一个合成层。
这么做的原因有三个:
首先当然是合并一些 RenderLayer 层,这样可以减少内存的使用量;
其二是在合并之后,尽量减少合并带来的重绘性能和处理上的困难;
其三对于那些使用单独层能够显著提升性能的 RenderLayer 对象,可以继续使用这些好处。
为什么一个 RenderLayerBacking 对象需要这么多层?原因有很多,例如,WebKit 需要将滚动条独立开来称为一个层,需要两个容器层来表示 RenderLayer 对应的 Z坐标为正数的子女和 Z 坐标为负数的子女,需要滚动的内容建立新层,还可能需要剪裁层和反射层。
图 8-4 中的树状结构描述了所有层的绘制顺序,按照先根顺序遍历的结果即是绘制顺序,图中每个层就是一个 GraphicsLayer 对象。
管理这些合成层等工作的是 RenderLayerCompositor 类,可以说是个 “大管家”。
1.3 硬件渲染过程
首先,WebKit 决定哪些些是合成层并为它们分配后端存储。
其次,WebKit 需要遍历和绘制每一个合成层,也就是每个合成层可能有一个或者多个 RenderLayer 对象。
最后,渲染引擎将所有绘制完的合成层合成起来,这个是由 WebKit 的移植来完成的。
1.4 3D 图形上下文
WebKit 中的 3D 图形上下文主要是提供一组抽象接口,这组接口能够提供类似 OpenGLES(使用 GPU 硬件能力的 3D 图形应用编程接口)的功能,其主要目的当然是使用 OpenGL 绘制 3D 图形的能力。这一层抽象能够将 WebKit 各个移植的不同部分隐藏起来,WebCore 只是使用统一的抽象接口。在 WebKit 中,3D 图形上下文的主要用途是 WebGL,当然启用硬件加速的 Canvas2D 等 HTML5 技术也会使用 3D 图形技术,不过情况有些不同。
1.2 Chromium 的硬件加速机制
1.2.1 GraphicsLayer 的支持
GraphicsLayer 对象是对一个渲染后端存储中某一层的抽象,同众多其他 WebKit 所定义的抽象类一样,在 WebKit 移植中,它还需要具体的实现类来支持该类所要提供的功能。
1.2.2 框架
在 Chromium 中,所以使用 GPu 硬件加速(也就是调用 OpenGL编程接口)的操作都是由一个进程(称为 GPU 进程)负责来完成的,这其中包括使用 GPU 硬件来进行绘图和合成。
Chromium 是多进程架构,每个网页的 Renderer 进程都是将之前介绍的 3D 绘图和合成操作通过 IPC 传递给 GPU 进程,由它来统一调度并执行。
在 Chromium 的 Android 版本中,GPU 进程并不存在, Chromium 是将 GPU 的所有工作放在 Browser 进程中的一个线程来完成,这得益于结构设计的灵活性。但是本质上,GPU 进程和 GPU 线程并无太大区别。
上图 描述了 Chromium 的多进程架构中 GPU 进程同其他进程之间的联系,事实上每个 Renderer 进程都依赖 GPU 进程来渲染网页,当然 Browser 进程也会同 GPU 进程进行通信,其作用是创建该进程并提供网页渲染过程最后绘制的目标存储。
那么 GPU 进程和 Render 进程是如何同步这些命令的呢?答案是,GPU 进程处理一些命令后,会向 Renderer 进程报告自己当前的状态, Renderer 进程通过检查状态信息和自己的期望结果来确定是否满足自己的条件。
GPU 进程最终绘制的结果不再像软件渲染那样通过共享内存传递给 Browser 进程,而是直接将页面的内容绘制在浏览器的标签窗口内。
1.2.3 命令缓冲区
命令缓冲区(Command Buffer)主要用于 GPU 进程和 GPU 的调用都进程传递 GL 操作命令。从接口上来讲,这一设计只提供一些基本的接口来管理缓冲区,宽并没有对缓冲区的具体方式和命令的类型进行任何限制,不过目前 Chromium 只有 GLES 一种实现方式。
1.2.4 Chromium 合成器(Chromium Compositor)
合成器的作用就是将多个合成层合成并输出一个最终的结果,所以它的输入是多个待合成的合成层,每个层都有一些属性(如 3D 变形等),它的输出就是一个后端存储,例如一个 GPU 的纹理缓冲区。
Chromium 合成器是一个独立并且复杂的模块,它的作用是合成网页划分后的合成层。
总结
- 硬件加速是指用GPU的硬件能力来渲染网页,GPU的主要作用是用来绘制3D图形并且有很好的性能,对于GPU绘图而言,通常不像软件渲染那样只是计算其中更新的区域,一旦有新的更新请求,如果没有分层,引擎可能会重新绘制所有的区域,因为计算更新部分对GPU来说可能耗费更多的时间,当网页分层之后,部分区域的更新可能只在一层或几层,而不需要更新整个网页,通过重新绘制网页的一个或几个层,并将它们和其他之前绘制完的层合成起来,既能使用GPU的能力,又能够减少重绘的开销。