GPU:合成加速

公司内部同事翻译的一篇技术文章,很多干货,讲解了浏览器渲染的一些原理的东西,值得一读。

去年翻译了一篇google的文章:GPU Accelerated Compositing in Chrome,最近翻出来重看了一下,重新整理了下,原文很长,对原文进行了一些整理和删减,想看原文请戳上面链接~

以下均针对chrome

硬件(GPU)加速加速的是什么?

是层的合成(layer composite),我们常说的使用translate3D开启硬件加速,其实是使应用了translate3D的元素获得独立的GraphicsLayer,那拥有独立的GraphicsLayer有啥好处呢?简单来说:

  • 每个GraphicsLayer都有一个GraphicsContext,GraphicsContext会输出该层的位图,交由GPU合成,比CPU要快
  • 当需要repaint时,只需要repaint自己,不会影响到其他的GraphicsLayer。repaint完之后,只需要通过GPU同其他层合并下(composite layers)

所以说,在我们平常做动态效果时,会使用translate3D的hack,利用GPU的合成加速来获取更好的效果和性能。

下面详细讲述下chrome中硬件加速合成

1、简介:为什么使用硬件进行合成(compositing)

传统方式下,浏览器依赖于CPU来渲染页面内容。而随着GPU硬件能力的不断发展(包括一些小型设备,比如手机的硬件能力也有很大提升),开始试图使用GPU的硬件能力来获得更好的性能和更少的电量消耗。使用GPU来渲染合成页面内容可以获得明显的速度的提升。

GPU在合成页面layer上的效率要比CPU高很多,尤其是涉及大量像素的绘制(drawing[1])和合成(compositing)操作。(GPU:人家设计出来本来就是做这些工作的好伐(lll¬ω¬))

2、渲染基础

比较基础的内容,已经了解,可以直接略过

Blink Rending 引擎(由Google和Opera Software开发的浏览器排版引擎)的源码十分繁多和复杂。为更好理解在chrome中GPU加速是如何工作的,我们首先需要了解Blink引擎是如何渲染页面的。

Nodes和DOM树

在Blink引擎中,页面内容是存储为由Node对象组成的树状结构,也就是DOM树。每一个HTML element元素都有一个Node对象与之对应,DOM树的根节点永远都是Document Node。

从Nodes到RenderObjects

DOM树中得每个node节点都有一个对应的RenderObject对象。RenderObject存储在与DOM树相对应的树形结构中——Render Tree。RenderObject知道如何在屏幕上paint Node内容,当然为实现这一操作,RenderObject会调用其对应的GraphicsContext来执行必要的draw操作。其中,GraphicsContext就是负责将像素写入位图(bitmap)中,这些位图最终会展示在屏幕上。

PS:draw != paint,draw是将像素点绘制到屏幕上(将最终的屏幕画面放到屏幕上),而pain是生成元素呈现的像素,例如,一个有着灰色背景,有文字的元素,当浏览器paint它时,是决定哪些像素填充背景,哪些像素填充文字,然后浏览器将这些像素存入位图(bitmap)中。

从RenderObjects到RenderLayers

每一个RenderObject都直接或间接(通过其父对象)的同一个RenderLayer相关联。

RenderLayer的作用就是保证页面元素以正确的顺序合成(composited),这样才能正确的展示元素的重叠以及半透明元素等等。会有一些情形,为一些特殊的RenderObjects创建一个新的RenderLayer。以下是常见的一定会新建RenderLayer的RenderObject:

  • 页面的根节点对应的RenderObject
  • 有明确的CSS定位属性(relative,absolute或者transform)
  • 是透明的
  • 有CSS overflow、CSS alpha遮罩(alpha mask)或者CSS reflection
  • 有CSS 滤镜(fliter)
  • canvas元素对应的RenderObject
  • video元素对应的RenderObject

对于上述特殊的RenderObjects,它对应于为它所创建的新的RenderLayer,而其他的RenderObjects与其最近的拥有RenderLayer的父级RenderObject,对应于同一个RenderLayer(有点拗口,就是说他本身没有,他就去他的父级找,直到找到的第一个)

RenderLayer也是一个树状的结构。根节点就是页面根元素所对应的RenderLayer,视觉上,每一个layer节点的后代都包含在父layer中。每一个RenderLayer的子layer保存在两个升序排列的列表中,分别是negZOrderList(负z-index的layers,即当前layer之下的layer)和posZOrderList(正z-index的layers,即当前layer之上的layer)

从RenderLayers到GraphicsLayers

为利用合成器(compositor,下一章会介绍),满足如下条件的RenderLayers,会被认为是一个独立的合成层:

  • 有3D或者perspective transform的CSS属性的层
  • video元素的层
  • canvas元素的层
  • flash
  • 对opacity和transform应用了CSS动画的层
  • 使用了CSS滤镜(filters)的层
  • 有合成层后代的层
  • 同合成层重叠,且在该合成层上面(z-index)渲染的层

如果RenderLayer是一个合成层,那么它有属于它自己的单独的GraphicsLayer,否则它和它的最近的拥有GraphicsLayer的父layer共用一个GraphicsLayer。如同RenderObject和Renderlayer的关系一样。

每一个GraphicsLayer都有一个GraphicsContext,其对应的RenderLayer会paint进GraphicsContext中。合成器(compositor)最终会负责,将由GraphicsContext输出的位图(bitmap)合并成最终屏幕显示的图案。

层压缩(Layer Squashing)

所有的规则都会有漏洞。正如上面提到的,GraphicsLayers会消耗内存和其他资源(随着GraphicsLayer树的大小增长,会使CPU执行时间越来越长)。当一些RenderLayer同一个成为独立合成层的RenderLayer重叠时,就会产生大量的额外的合成层(上节合成层产生条件的最后一条),十分消耗资源。

我们把产生合成层的自身因素(比如有3D变换的层)称之为直接因素,将比如:合成层后代,同合成层重叠等条件成为简介因素。为了防止上述所说的“层爆炸”,当很多element覆盖在因直接因素产生的层之上时,浏览器的排版引擎,会将这些element的RenderLayers覆盖在这个直接因素产生的RenderLayer上,同时将他们压缩(squash)成一个“层”。这就防止了由覆盖引起的层爆炸。更多细节请看这里和这里

从GraphicsLayers到WebLayers再到CC Layers

chrome通过WebLayer和cc layer(chrome compositor layer)实现GraphicsLayers。

小结

总的来说,为rendering服务的有如下四种树形结构:

  • DOM Tree,基本的模型
  • RenderObject Tree,同DOM树的可见节点是一一对应的。RenderObject知道如何去paint其相对应的DOM节点
  • RenderLayer Tree,由RenderLayers组成,这些RenderLayer对应于RenderObject树的RenderObject。这种对应关系是一对多的。
  • GraphicsLayer Tree,由GraphicsLayers组成,这些GraphicsLayer对应于RenderLayer树的RenderLayer。这种对应关系是一对多的。

如下图所示:

GPU:合成加速_第1张图片

之后文章中所说的层(layer)都代指cc layer(chrome compositor layer:GraphicsLayer在chrome中得实现)。合成器(Compositor)操作的是cc layer。

3、合成器(The Compositor)

chrome合成器(Compositor)是一个软件库,用来管理GraphicsLayer树

简介

回想一下,rendering有两个阶段:先是paint然后composite。合成器会在每个层的基础上执行一些额外的工作。比如,在层的位图合成前,合成器会负责对这些层的位图执行一些必要的transform(比如层的CSS tranform属性指定的变换)。另外,因为层的painting[2]和compositing解耦了,所以一个invalidating[3](需要repainting的)层只会重绘(repainting)自己的内容,然后所有层重新进行合成(recopositing)。

合成器还会负责将层合成绘制为最终的屏幕画面。

GPU是干啥的?

GPU是如何发挥作用的呢?合成器会使用GPU来执行它的合成绘制的步骤。这和老的软件渲染模型有显著的区别,在老的模型中,Render进程将页面内容的位图(bitmap)通过IPC(进程间通信)和共享内存的方式来传给浏览器进程去呈现。

而在硬件加速体系结构中,合成由GPU负责。合成器本质上也是使用GPU将层绘制成一个位图,最后输出到屏幕上。

4、GPU进程

合成是交由GPU完成的,那Render进程如何传递命令给GPU的呢?在chrome多进程模型中,有一个专门的进程负责这个任务:GPU进程。GPU进程的存在主要是因为安全原因。需要注意的是,android是个例外,Chrom会使用内置的GPU实现作为浏览器主进程的一个线程来运行。当然android上的GPU线程和其他平台的GPU进程的行为是一致的。

GPU进程体系有如下的好处:

  • 安全:render的逻辑大部分在沙盒渲染进程中,平台3D API的访问权限只对GPU进程开放。
  • 鲁棒:GPU进程档掉并不会使浏览器挂掉。
  • 一致性:作为浏览器渲染API的OpenGL ES 2.0是标准化的,跨平台的。
  • 并行:Render进程可以快速 的将命令发给命令缓冲区,并且返回到CPU密集的render活动中,留给GPU进程去处理这些命令。我们可以充分利用多内核机器上的GPU进程和CPU进程。

具体的工作原理就不说了(自己也一知半解...),有一点需要注意,

一些较大的资源,比如位图,Render进程和GPU进程是通过共享内存传递的,也就是说合成层是会占用内存(显存)的,所以像translate3D这样的hack不要过度使用,一旦用了,就会产生一个合成层,就会占用内存(显存),浏览器是很智能,有一套生命周期和共享的管理机制,但也架不住滥用的,尤其是在手机上,大部分手机的显存和内存是共享的,占用过多,手机会变卡的,所以hack虽好,可不要滥用哦。

5、合成线程

在chrome为渲染服务的有两个进程:Render进程和GPU进程。GPU进程负责Render进程和GPU之间的命令的传递,Render进程包括主线程和合成线程,在chrome中,paint和composite放在了合成线程中。

之前说过,合成器是一个软件库,它最初是运行在Renderer主线程中,然后转移到了自己的线程中(也就是所谓的合成线程)。

合成线程和主线程是相互独立的,合成线程持有一份主线程层树(layer tree)的副本,主线程和合成线程的层数是独立的,他们之间通过消息机制保持同步,这意味着主线程可以专注于运行javascript,而合成线程则专注于处理paint和composite

一些输入事件(主要是scroll)会首先从浏览器进程转移到合成线程,然后从合成线程转交给Renderer主线程。通过控制输入和输出,合成器可以保证对用户输入的第一时间进行视觉响应。

有趣的是,这种体系结构导致了javascript的touch事件回调中调用preventDefault()可以阻止滚动,而scroll事件的却不可以。如果javascript要取消touch事件,那么合成器在javascript回调(运行在主线程中)完成前是不会滚动页面的。而对于scroll事件,会先转移到合成线程,合成线程会立刻开始滚动,只是将一些诸如滚动偏移量的信息异步传给主线程,而不管主线程是否立刻处理滚动事件。

chrome在渲染时做了很多优化:

  • 只栅格化(矢量=>位图)可视区域的层。用户滚动时,新的可视区域的内容如果还没完全栅格化准备好,用户可能会看到一片空白区域,为了缓解这一情况,chrome会先将呈现内容栅格化为低分辨率的展示出来,等高分辨率的准备好后再替换
  • 栅格化整个层会十分浪费CPU时间(paint操作的时间)和内存,所以chrome会把层分解为更小的单位:切片(tile,可看成一个矩形区域),并且在这些切片的基础上栅格化层。切片根据一系列因素来确定优先级,包括,靠近视口的程度和在屏幕上显示的预估时间。GPU内存会根据切片的优先级,首先分配给高优先级切片
  • 双指缩放和在fast fling(快速滚动)期间,会使用低分辨率切片
  • 合成器会拦截pinch/zoom的输入事件,并且在GPU上按适当比例缩放已经栅格化的切片

6、术语表

[1] drawing:将像素点绘制到屏幕上(将最终的屏幕画面放到屏幕上)的render阶段

[2] painting:RenderObjects调用GraphicsContext API生成相应的视觉展现的render阶段。生成元素呈现的像素,例如,一个有着灰色背景,有文字的元素,当浏览器paint它时,是决定哪些像素填充背景,哪些像素填充文字,然后浏览器将这些像素存入位图(bitmap)中。

[3] invalidation:标记为dirty的文档区域,一般表示该区域需要repainting。样式系统(style system)也有相同的概念。


你可能感兴趣的:(web前端)