Chrome渲染优化:层模型

开发 前端
近几年,现代浏览器利用图形卡对页面渲染方式进行了改进:这种改进一般都笼统地称为“硬件加速”。当谈及普通Web页面(也即:非Canvas2D或者WebGL页面)时,硬件加速这个词到底意味着什么?本文将对作为Chrome中硬件加速下Web页面渲染基础的基本模型进行说明。

引言

对于大多数Web开发者而言,Web页面的基本模型就是DOM模型。页面的渲染过程常常并不为人所知,大家只是知道它是一个将页面的DOM表示方式转化为在屏幕上显示的一个图片的过程。近几年,现代浏览器利用图形卡对页面渲染方式进行了改进:这种改进一般都笼统地称为“硬件加速”。当谈及普通Web页面(也即:非Canvas2D或者WebGL页面)时,硬件加速这个词到底意味着什么?本文将对作为Chrome中硬件加速下Web页面渲染基础的基本模型进行说明。

冗长的注意事项

我们在这里讨论的是WebKit,更具体地讲,我们讨论的是Chromium下的WebKit。 本文所涉及的是Chrome的实现细节,而不是Web平台的特性。Web平台及其标准并没有对这个层次的实现细节做出详细的规定,所以我们并不能保证本文中的内容可以适用于其它的浏览器,但是对浏览器内部工作机理的了解定会有益于对应用进行高水平的错误调试和性能调优。

另外还要主意,整个这篇文章讨论的都是Chrome渲染架构中的一个核心部分,但这个结构仍在迅速变化之中。本文试图仅仅涉及那些不太可能发生变化的部分,但要是在6个月后再看这篇文章,那可就不一定是什么情况了。

很重要的是要明白Chrome当下有段时间还会有两个不同的渲染途径:硬件加速式的渲染和早期软件式的渲染。直到写这篇文章之时为止,在Windows、 ChromeOS和Android下的Chrome中,所有的页面都走的是硬件加速途径。在Mac和Linux下, 只有那些部分内容需要组合的页面才会走硬件加速途径(更多关于什么才需要组合的说明请参见下文),但用不了多久,所有页面将都会走硬件加速途径。

最后要说的是,我们在这里的内容只是对Chrome的渲染引擎的管窥之见,所看到的只是其对性能影响比较大的一些特性。当你想要提高你的网站的性能时,对层模型有所了解会非常有帮助作用,但这也容易造成搬起石头砸自己脚的情况:层这种结构非常有用,但是创建过多的层会造成整个图形栈开销的增加。到时候可别说我没有提前警告过你哦!

从DOM到屏幕

层的引入

页面一旦在装入并解析完成后,就会表示为许多Web开发者所熟悉的结构:DOM。然而,在页面的渲染过程中,浏览器还具有一系列并不直接暴露给开发者的页面中间表示方式。这些表示方式中最重要的结构就是层。

在Chrome中实际上有几种不同类型的层:掌管DOM子树的渲染层(RenderLayer)以及掌管渲染层子树的图形层(GraphicsLayer)。 我们这里最关心的是后者,因为作为纹理上传到GPU之中的就是图形层。本文自此就只用“层”来指代图形层了。

稍稍离题说一下同GPU有关的几个术语:什么是纹理?纹理可以看作是位图图像,从主存储器(也就是RAM)传递到视频存储器(也就是GPU之上的 VRAM)之中的就是这个图像。一旦传递到GPU之中后,你就能够将纹理映射到一个网格几何结构之上 —— 在视频游戏或者CAD程序中,这种技术用来给框架式的3D模型添加“皮肤”。Chrome采用纹理把页面中的内容分块发送给GPU。纹理能够以很低的代价映射到不同的位置,而且还能够以很低的代价通过把它们应用到一个非常简单的矩形网格中进行变形。这就是3D CSS的实现原理,而且这么做对页面在屏幕的快速滚动也非常好 —— 现在先说这些,这两方面更详细的探讨情况下文。

下面让我们用几个例子来说明层的概念。

在Chome中研究层时有一个非常有用的工具就是Chrome的开发者工具里设置(也就是那个小齿轮图标)中“渲染(rendering)”小标题下的 “显示层的组合边界(show composited layer borders)”开关。让我们把这个开关打开。本文中所有的截屏和例子都来自最新版的Chrome Canary,在写这篇文章的时候是Chrome 27。

图1: 只有一层的页面 (将在新窗口中打开)

  1. <html> 
  2.   <body> 
  3.     <div>I am a strange root.</div> 
  4.   </body> 
  5. </html> 

译者注:这里缺了一个图,原文中的图就看不到,可能是需要翻墙?

在页面的基本层中组合层的渲染边界屏幕截图

这个页面只有一层。蓝色的栅格表示的是分块,这些分块可以看作是比层更低一级的单位,Chrome以这些分块为单位,一次向GPU上传一个分块的内容。这里它们并不怎么重要。

图2:有自己的层的元素 (open stand-alone)

  1. <html> 
  2.   <body> 
  3.     <div style="-webkit-transform: rotateY(30deg) rotateX(-30deg); width: 200px;"> 
  4.       I am a strange root.  
  5.     </div> 
  6.   </body> 
  7. </html> 

译者注:这里缺了一个图,原文中的图就看不到,可能是需要翻墙?

旋转后层的渲染边界的截屏

在<div>上加上让它旋转一个角度的3D CSS属性后,我们就能够看到一个元素有自己的层是个什么样子:请注意其中的橘色边界,这个边界给出了这个视图中层的轮廓。

层的创建准则

还要其它什么元素会得到自己的层?Chrome在这方面采用的规则仍在随着时间推移逐渐发展变化,但在目前下面这些因素都会引起Chrome创建层:

  • 进行3D或者透视变换的CSS属性
  • 使用硬件加速视频解码的<video>元素
  • 具有3D(WebGL)上下文或者硬件加速的2D上下文的<canvas>元素
  • 组合型插件(即Flash)
  • 具有有CSS透明度动画或者使用动画式Webkit变换的元素
  • 具有硬件加速的CSS滤镜的元素
  • 子元素中存在具有组合层的元素的元素(换句话说,就是存在具有自己的层的子元素的元素)
  • 同级元素中有Z索引比其小的元素,而且该Z索引比较小的元素具有组合层(换句话说就是在组合层之上进行渲染的元素)

实际意义:动画

我们还可以将层在页面中到处移动,正好可用于动画。

图3: 动画层(将在新窗口中打开

  1. <html> 
  2. <head> 
  3.     <style type="text/css"> 
  4.     div {  
  5.         -webkit-animation-duration: 5s;  
  6.         -webkit-animation-name: slide;  
  7.         -webkit-animation-iteration-count: infinite;  
  8.         -webkit-animation-direction: alternate;  
  9.         width: 200px;  
  10.         height: 200px;  
  11.         margin: 100px;  
  12.         background-color: gray;  
  13.     }  
  14.     @-webkit-keyframes slide {  
  15.         from {  
  16.             -webkit-transform: rotate(0deg);  
  17.         }  
  18.         to {  
  19.             -webkit-transform: rotate(120deg);  
  20.         }  
  21.     }  
  22.     </style> 
  23. </head> 
  24. <body> 
  25.     <div>I am a strange root.</div> 
  26. </body> 
  27. </html> 

正如前文所述,层在将Web页面中的静态内容随处移动方面真的很有用。在最基本的情况下,Chrome将层中的内容绘制到软件位图中,然后再将该位图作为纹理上载到GPU之中。如果层中的内容将来不会发生变化,那就不需要对其进行重绘了。这是个好事:重绘将会占用本可以用于干其它事情,比如运行 JavaScript的时间,而且如果绘制过程太长,动画还会出现卡顿现象。

例如,可以在Chrome的开发工具中看一下这个页面的时间线:但该层在来回旋转的时候,并没有出现绘制操作。 动画过程中的开发者工具时间线屏幕截图

无效!重绘

但是如果层中的内容发生了改变,它就需要重绘了。

图4:层的重绘 (将在新窗口打开)

  1. <html> 
  2. <head> 
  3.   <style type="text/css"> 
  4.   div {  
  5.       -webkit-animation-duration: 5s;  
  6.       -webkit-animation-name: slide;  
  7.       -webkit-animation-iteration-count: infinite;  
  8.       -webkit-animation-direction: alternate;  
  9.       width: 200px;  
  10.       height: 200px;  
  11.       margin: 100px;  
  12.       background-color: gray;  
  13.   }  
  14.   @-webkit-keyframes slide {  
  15.       from {  
  16.           -webkit-transform: rotate(0deg);  
  17.       }  
  18.       to {  
  19.           -webkit-transform: rotate(120deg);  
  20.       }  
  21.   }  
  22.   </style> 
  23. </head> 
  24. <body> 
  25.   <div id="foo">I am a strange root.</div> 
  26.   <input id="paint" type="button" onclick="" value=”repaint”> 
  27.   <script> 
  28.     var w = 200;  
  29.     document.getElementById('paint').onclick = function() {  
  30.       document.getElementById('foo').style.width = (w++) + 'px';  
  31.     }  
  32.   </script> 
  33. </body> 
  34. </html> 

每次点击按钮后,旋转中的元素的宽度就会增加1px。这将导致页面重新布局,整个元素都需要重绘,在这个例子中需要重绘的是整个层。

#p#

查看Chrome重绘了哪些元素的一个很好的办法是使用开发者工具中的“显示绘制矩形”开关,这个开关同样也在开发者工具设置中的“渲染”标题下。打开该开关后,在点击按钮的时候请注意动画中的元素和按钮周围都会有一个红色的矩形闪现。

Screenshot of show paint rects checkbox

显示绘制矩形检查框的屏幕截图

同时绘制时间也出现在开发者工具里的时间线中了。明眼的读者可能还注意到这里有两个绘制事件:一个是层的,另外一个是按钮的。按钮会在它的状态变成按下状态和从按下状态变为非按下状态时需要进行重绘。

Screenshot of Dev Tools Timeline repainting a layer

开发者工具时间线中层的重绘的屏幕截图

请注意Chrome并不总是需要重绘整个层,它会尽量地以聪明的方式只绘制DOM中发生变化的那部分内容。在我们的这个例子中,我们所改变的DOM元素是整个层的尺寸。但是在很多情况下,在一层中会有大量的DOM元素。

很显然接下来的问题是页面内容失效和强制进行重绘是由什么引起的。要全面回答这个问题并不容易,因为引起强制进行重绘的有大量不太容易区分的情况。其中最常见的原因是通过操纵CSS样式或者引起重新进行页面布局来改变DOM的特性。Tony Gentilcore写了一篇很不错的讨论引起页面重新布局的原因的博文,Stoyan Stefanov有一篇更近详尽地讨论浏览器绘制过程的文章(但这篇文章仅仅止于绘制过程,并没有涉及奇特的组合部分的内容)。

找出重绘是否影响到了某些你正在关注中的元素的最好方式是使用开发者工具中的时间线以及“显示绘制矩形”工具,用这两个工具能够看出浏览器是否在重绘一些你并不认为需要重绘的内容,然后找出就在重新布局/重绘前的那个时刻之前到底是在哪里改变了DOM结构。如果绘制过程无法避免但绘制过程却长的不太合理,请参考一下Eberhard Gräther的文章,这是一篇关于在开发者工具中如果对持续性绘制模式进行性能优化的文章。

总结:从DOM到屏幕

Chrome是如何将DOM转换为屏幕上的图像的呢?从概念上讲,它:

  1. 取得DOM并将其分成若干个层
  2. 分布将这些层绘制到各自的软件位图中
  3. 将绘制好的位图作为纹理上载至GPU之中
  4. 将这些不同的层组合起来形成屏幕上最后显示出的图像

这些步骤在Chrome第一次产生Web页面的帧时都需要执行。但是在产生随后的帧时,就可能会走一些捷径:

  • 如果有某些CSS属性发生了改变,就并不一定要重绘所有的内容了。Chrome能够将已经作为纹理保存在GPU之中的层重新组合起来,但只是在重新组合时,使用不同的组合属性(比如,在不同的位置、以不同的透明度来组合等等)。
  • 如果某一层中的某个部分的内容变成无效的了,那么该层就需要重绘并在重绘完成后重新上载至GPU中。如果其内容仍然不变,只是其组合属性发生了变化(比如它的位置或者透明度改变了),Chrome就不会对GPU中该层的位图做任何处理,只是通过重新进行组合来生成新的帧。

现在大家应该弄清楚了,基于层的组合模型对渲染性能有着非常大的意义。在没有需要重新绘制的内容时,组合的代价相对来说代价更低一些,所以在调试渲染性能时,避免层的重绘是一个非常好的总体目标。经验老道的开发者会去看上文提到的组合触发的列表,并意识到很可能会非常容易的导致浏览器创建很多层。但是,一定要小心无意识地去创建层,因为使用层是有代价的:它们会占用系统RAM以及GPU中的存储空间(移动设备上的存储空间特别有限),层太多的话还会在跟踪哪些从可见哪些层不可见的算法中引入额外的开销。如果层都很大而且原先不重叠的层突然重叠了起来,那么太多的层就会增加浏览器花在栅格化方面的时间,这会导致有时称之为“过度绘制”的情况。所以,一定要明智地运用你所学到的知识!

暂时先写到这里了。请你以后再到这里来查看另外几篇关于层模型实际意义的文章。

其它参考资料

  1. Scrolling Performance
  2. Profiling Long Paint Times with DevTools' Continuous Painting Mode
  3. http://jankfree.com
  4. http://aerotwist.com/blog/on-translate3d-and-layer-creation-hacks/

英文原文:Accelerated Rendering in Chrome - The Layer Model

译文链接:http://www.oschina.net/translate/chrome-accelerated-rendering

责任编辑:林师授 来源: OSCHINA编译
相关推荐

2017-05-10 14:47:37

Headless Ch页面 Docker

2023-04-10 11:18:38

前端性能优化

2015-09-16 13:54:30

Android性能优化渲染

2009-07-15 13:48:26

Swing模型和渲染器

2016-12-08 10:57:08

渲染引擎前端优化

2023-11-18 19:46:07

GPU架构

2022-12-12 09:01:13

2023-03-22 18:31:10

Android页面优化

2022-06-06 22:36:55

渲染性能CSS

2018-06-27 08:21:31

前端Web渲染

2018-01-19 14:39:53

浏览器页面优化

2017-10-09 13:39:26

浏览器渲染服务器

2017-04-25 16:20:10

页面优化滚动优化

2019-07-16 10:42:02

网络模型TCP

2019-07-09 13:54:19

网络模型网络协议TCP

2009-11-26 10:50:22

2016-08-12 10:23:28

javascriptChrome前端

2014-06-17 09:30:14

OSI

2009-11-26 10:51:40

Chrome OS微软称IE9

2015-11-25 09:04:54

艺龙网前端domdiff
点赞
收藏

51CTO技术栈公众号