硬件加速基础
对于 GPU 绘图而言,通常不像软件渲染那样只是计算其中更新的区域,一旦有更新请求,如果没有分层,引擎可能需要重新绘制所有的区域,因为计算更新部分对 GPU 来说可能耗费更多的时间。当网页分层以后,GPU 渲染只需要绘制对应的 RenderLyaer 对象的所有内容(对于 TileLayer 不是这样的情况),并将它们和之前的层合成起来即可。
硬件渲染机制在 RenderLayer 树建立之后需要做三件事来完成之后的网页渲染:
- 将某些不满足条件的 RenderLayer 对象组合在一起,形成一个有后端存储的新层(合成层,Compositing Layer),用于之后的合成。对于一个 RenderLayer 对象,如果它没有后端存储的新层,那么就用它的父亲所使用的合成层。
- 将每个合成层包含的 RenderLayer 内容绘制在合成层的后端存储中,这里的绘制可以是软件绘制也可以是硬件绘制。
- 使用合成器将多个合成层按照这些层的前后顺序、合成层的 3D 变形等设置而合成最终图形。
如果一个 RenderLayer 对象被 Webkit 依照一定规则创建了后端存储,那么该 RenderLayer 被称为合成层。每个合成层都有一个 RenderLayerBacking,RenderLay erBacking 负责管理 RenderLyaer 所需要的所有后端存储。
如果一个 RenderLayer 对象具有一下的特征之一,那么它就是合成层:
- RenderLayer 具有 CSS 3D 属性或者 CSS 透视效果。
- RenderLayer 包含的 RenderObject 节点表示的是“video”元素。
- RenderLayer 包含的 RenderObject 节点表示的是“canvas”元素。
- RenderLayer 使用了 CSS 透明效果的动画或者 CSS 变换的动画。
- RenderLayer 使用了硬件加速的 CSS Filter 技术。
- RenderLayer 使用了裁剪(Clip)或者反射(Reflection)属性,并且它的后端包括一个合成层。
- RenderLayer 有一个 Z 坐标比自己小的兄弟节点,且该节点是一个合成层。
RenderLayerBacking 包含的各种 GraphicsLayer(存储空间)对象层:
为什么 RednerLayerBacking 对象需要这么多层呢?原因有很多,例如 Webkit 需要将滚动条独立开来称为一个层,需要两个层来表示 RenderLayer 对应的 Z 坐标和为正数和 Z 坐标为负数的子女,需要为滚动内容建立新层,还可能需要裁剪层和反射层。
每个 RenderView(Document)对象包含一个 RenderLayerCompositor 对象,这个对象仅在硬件加速机制下才会被创建,用来管理所有的合成层,计算和决定哪些 RenderLayer 对象是合成层,而且需要为这些合成层创建 GraphicsLayer 对象。其本身类似一个 RenderLayerBacking 对象,也包含一些 GraphicsLayer 对象,这些 GraphicsLayer 对象对应的是整个网页所需要的后端存储。
对于 canvas 元素的合成层,其内容由视频解码器来绘制,而后通过定时器或者其他机制来告诉 Webkit 该层内容已经发送更改,需要重新合成。
Webkit 中的 3D 图形上下文主要是提供一组抽象接口,这一层抽象能够将 Webkit 各个移植的不同部分隐藏起来,WebCore 只是使用统一的抽象接口。
Chromium 的硬件加速机制
在 chromium 中,所有使用 GPU 硬件加速的操作都是由一个进程(GPU 进程)负责完成的,这其中包括使用 GPU 硬件来进行绘图和合成。Chromium 是多进程架构,每个网页的 Render 进程都是将 3D 绘图操作和合成操作通过 IPC 传递给 GPU 进程,由它来统一调度并执行。在 Chrome 的 Android 版本中,GPU 进程并不存在,Chrome 是将 GPU 的所有工作放在 Browser 进程中的一个线程来完成。
和软件渲染不同,GPU 进程最终绘制结果不再像软件渲染那样通过共享内存传递给 Browser 进程,而是直接将页面的内容绘制在浏览器的标签窗口内。
Chromium 合成器(Chromium Compositor)
Webkit 对合成层的各种设置,最后都是使用 Layer 树来表示,每个 Layer 节点包含 3D 变形、裁剪等属性,但是 Chromium 将这些属性应用到后端存储并合成这一过程并不是在 Layer 树中进行,而是将这些功能委托给 LayerImpl 树来完成,两者之间通过代理来同步,代理的作用是协调和同步两者之间的这些操作。
合成器的表示和实现分离架构:
实现部分作为单独的一个线程是在 Render 进程中用来合成网页的,通常也称为合成器线程。
合成器的组成:
- 事件处理部分。主要是接收 Webkit 或者其他的用户事件,例如网页滚动、放大缩小等事件,这些事件会请求合成器重新绘制一个合成层,然后合成器再合成这些层的绘制结果。
- 合成层的表示和实现。主要定义各种类型的合成层,包括它们的位置、滚动、位置、颜色等属性。
- 合成层组成两种类型的树,以及它们之间的同步等机制。
- 合成调度器:用来调度来自用户的请求,它包括一个状态用于调度当前队列中需要执行的请求,目的是协调合成层的绘制和合成、树的同步等操作。
- 合成器的输出结果。在 Chromium 合成器中,结果可以是一个 GPU Surface 或者是一个 CPU 的存储空间。
- 各种后端存储等资源。
- 支持动画和 3D 变形这些功能所需要的基础设施。
合成器的 Layer 类和它的子类:
TileLayer 是一个中间类,它被 ContentLayer 类和 ImageLayer 类继承,它的含义是一个层的后端存储被分割成瓦片状(Tiles),由多个小后端存储共同存储而成。每个瓦片可以理解为 OpenGL 中的一个纹理对象,合成层的结果被分开存储在这些瓦片中。
ContentLayer 表示合成层使用 Skia 画布将内容绘制到结果中,对应网页中就是常见的 HTML 元素,例如 DOM 树中的 html、div 等所在的层。ImageLayer 表示图片所在的层,如果一个合成层仅仅包含一个图片,那么该图片也会使用瓦片存储技术。
使用瓦片存储的原因:
- DOM 树中的 html 元素所在的层可能会比较大,因为网页的高度很大,如果只是一个后端存储的话,那么需要一个很大的纹理对象,但是实际的 GPU 硬件只支持有限的纹理大小。
- 在一个比较大的合成层中,可能只是其中一部分发生变化,如果重新绘制整个层,会产生额外的开销,使用瓦片化的后端存储,就只需要重绘一些存在更新的瓦片。
- 当层发生滚动的时候,一些瓦片可能不再需要,然后 Webkit 需要一些新的瓦片来绘制新的区域,这些大小相同的后端存储很容易重复利用。
合成器内部是一个循环系统,状态机计算出下一个任务,调度器获得任务并执行该任务,然后接着计算下一个任务,如此循环,知道空闲为止。
合成工作主要有四个步骤,这些步骤都是由调度器调度:
- 创建输出结果的目标对象“Surface”,也就是合成结果的存储空间。
- 开始一个新的帧(Begin Frame),包括计算滚动和缩放大小、动画计算、重新计算网页的布局、绘制各个合成层等。
- 将 Layer 树中包含的这些变动同步到 LayerImpl 树中。
- 合成 LayerImpl 树中各个层并交换前后帧缓冲区,完成一帧的绘制和显示动作。
在这四个步骤中,步骤1只是在最开始的时候调用,而且是一次性的动作。当后面网页出现动画或者 JavaScript 代码修改 CSS 样式和 DOM 等情况的时候,一般会执行后面三个步骤,当然也可能只需要步骤4。
每个网页可能需要一个合成器,网页中的 iframe 也需要一个合成器,整个网页同浏览器的合成也需要一个合成器,这些合成器构成一个层次化的的合成器结构。根合成器是浏览器最高层的合成器,该合成器负责网页和浏览器用户界面的合成。
减少重绘
计算布局和绘图比较费时间,而合成需要的时间相对少一些。
使用 CSS 3D 变形技术,它能够让浏览器仅仅使用合成器来合成所有的层就可以达到动画效果,而不是通过重新设置其他 CSS 属性并触发计算布局、重新绘制图形、重新合成所有层这一非常复杂的过程。Webkit 不需要大量的布局计算,不需要重新绘制,只需要修改合成时候的属性即可。当合成器需要合成的时候,每个合成层都可以设置自己的 3D 变形属性,这些属性仅仅改变合成层的变化参数,而不需要布局计算和绘图操作,可以极大地节省时间。
其他硬件加速模块
2D 绘图本身是使用 2D 的图形上下文,而且一般使用软件方式来绘制它们,也就是光栅化(Rasterize)的方法。但是,其实这些 2D 绘图操作也可以使用 GPU 也就是 3D 绘图来完成,使用 GPU 来绘制 2D 图形的方法称为 2D 图形的硬件加速机制。目前 2D 图形的硬件加速有两种应用场景,第一种是网页基本元素的绘制,针对的层次类型为 ContentLayer,它的后端存储是一个 2D 的画布对象;第二种是 canvas 元素,用来绘制 2D 图形。
一个“canvas”元素的对象只能绘制 2D 图形和 3D 图形中的一种,不能够同时绘制这两者。canvas 2D 可以使用软件方法来绘制,也可以使用 GPU 来绘制,如果使用硬件加速机制的话,Chromium 会创建一个 SkDeferredCanvas 对象,该对象使用延迟技术,会先保存多个 2D 图形操作,当 Chromium 需要绘制一个新的帧的时候,Skia 图形库才会一次性提交并绘制这些缓存的操作。