问题1:transform动画为什么没有经过大量的重绘?
解答:为什么 transform
没有触发 repaint 呢?(1)简而言之,transform
动画由GPU控制,支持硬件加速,并不需要软件方面的渲染。(2)浏览器接收到页面文档后,会将文档中的标记语言解析为DOM树。DOM树和CSS结合后形成浏览器构建页面的渲染树。渲染树中包含了大量的渲染元素,每一个渲染元素会被分到一个图层中,每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中transform
是不会触发 repaint 的,这一点非常类似3D绘图功能,最终这些使用 transform
的图层都会由独立的合成器进程进行处理。(3)3D 和 2D transform 的区别就在于,浏览器在页面渲染前为3D动画创建独立的复合图层,而在运行期间为2D动画创建。动画开始时,生成新的复合图层并加载为GPU的纹理用于初始化 repaint。然后由GPU的复合器操纵整个动画的执行。最后当动画结束时,再次执行 repaint 操作删除复合图层。(4)如果某一个元素的背后是一个复杂元素,那么该元素的 repaint 操作就会耗费大量的资源,此时也可以使用上面的技巧来减少性能开销(transform)。内容摘自CSS动画之硬件加速 CSS3 Filter的十种特效
问题2:chrome浏览器控制台可能牵涉的渲染过程?
recaculate style:这个过程是根据CSS选择器,比如.headline或.nav > .nav_item,对每个DOM元素匹配对应的CSS样式。这一步结束之后,就确定了每个DOM元素上该应用什么CSS样式规则。
layout:上一步确定了每个DOM元素的样式规则,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,
元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。
Paint Setup and Paint:本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的
update layer tree:更新RenderLayer树
composite layers:对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。
补充学习使用Chrome DevTools的Timeline和Profiles提高Web应用程序的性能 使用Chrome DevTools的Timeline分析页面性能 使用CSS3开启GPU硬件加速提升网站动画渲染性能 Google Chrome中的高性能网络(一) Chrome渲染分析之Timeline工具的使用
你可以仔细阅读Progressive Web App Dev Summit来了解CSS/JS->Style->计算样式->布局->绘制->渲染层合并的流程。同时使用transform/opacity实现动画效果一文也明确指出了如何通过上面的内容来提升动画的性能:
(1)只使用transform/opacity来实现动画效果。但是这时候提升动画性能(没有reflow/repaint)是有条件的,也就是动画元素必须独占一个层,这时候可以通过第二点来实现
(2)用`will-change`/`translateZ`属性把动画元素提升到单独的渲染层中
(3)避免滥用渲染层提升:更多的渲染层需要更多的内存和更复杂的管理,同时由于每个渲染层的纹理都需要上传到GPU处理,因此我们还需要考虑CPU和GPU之间的带宽问题、以及有多大内存供GPU处理这些纹理的问题。
(4)如果transform/opacity无法实现的动画,那么可以参考 FLIP principle。
下面属性的修改都会导致回流:
如上图position,font-family,vertical-align,clear,lin-height等都会导致页面的回流。外加上一个clip属性也会导致页面的回流和重绘
如果你动态修改上述任何一个属性都会导致重绘,同时他们所属的层会被传递到GPU中。在移动设备上是昂贵的,因为移动设备的GPU相比于桌面应用要弱小的多,而且CPU和GPU之间的通道有限,因此传输纹理可能需要很长的时间。
动画的良好表现是提升用户体验的法宝,因此我们应该尽量避免对那些会导致重绘和回流的元素进行动画设置,因为他们是昂贵的而且会导致丢帧的问题。最好提前声明动画,因为这样浏览器可以提前对动画进行优化。目前为止transform是最好的设置动画的属性,因此如果你的动画可以使用下列的属性来完成,那么就应该用下面的属性进行替换:
opacity,translate,rotate,scale。原文地址High Performance Animations 译文地址:前端性能优化(CSS动画篇)。当然,还有其他的属性也可能减少页面回流和重绘:
在《你不知道的Z-Index》中有提到,如果某个元素处于以下状态:
当一个元素位于HTML文档的最外层(元素)
当一个元素position不为initial,并且拥有一个z-index值(不为auto)
当一个元素被设置了opacity,transforms, filters, css-regions, paged media等属性。
(当然还会有其他情况)
那么就会产生一个新的渲染层(我觉得是是堆叠上下文,可以用于不同的composite layer之间的叠加次序),这时候执行动画,只需要GPU按照现有的位图,按照相应的变换在独立的渲染层中输出,然后再合并输出。这个过程并不需要主线程CPU的参与。你也可以阅读前端性能优化之更平滑的动画以及该文章引用的其他文章,但是个人认为,这里的堆叠上下文不是浏览器渲染时候的层的概念,只是元素排列时候堆叠次序。深入理解CSS中的层叠上下文和层叠顺序一文指出,层叠上下文只是为了解释元素发生重叠时候的表现形式,和我们这里说的分层的概念是完全不同的
减少chrome硬件加速的抖动问题:
-webkit-backface-visibility:hidden;
-webkit-perspective:1000;
3.网页的样式计算和布局计算?
renderobject:对于所有的可视节点(script,meta,head等除外)webkit都会建立renderobject对象,该对象保存了为绘制dom节点所必需的各种信息,例如样式布局信息,经过webkit处理后renderobject对象知道如何绘制自己。下面情况都会为dom节点建立renderobject对象:
dom树的document节点;dom树中的可视节点,如html,div等,webkit不会为非可视节点创建renderobject对象;某些情况下需要创建匿名的renderobject对象,其不对应dom树中任何节点,只是webkit处理上的需要,典型的就是匿名的renderblock节点
renderobject树:这些renderobject对象同dom节点类似,也构成一棵树,称为renderobject树。
注意:renderobject树时基于dom树建立的一颗新树,是为了布局计算和渲染等机制建立的一种新的内部表示.如果dom树中被动态添加了新的节点,webkit也需要创建相应的renderobject对象
注意:从上图可以看出htmldocument节点对应于renderview节点,renderview节点时renderobject树的根节点。同时head元素也没有创建renderobject对象
dom树建立之后->css解析器和规则匹配->renderobject树建立。css解析器和规则匹配处于dom树建立之后,renderobject树建立之前,css解释后的结果会保存起来,然后renderobject树基于该结果进行规范匹配和布局计算。当网页有用户交互和动画等动作的时候通过cssom等技术,js代码同样可以方便的修改css代码,webkit此时需要重新计算样式并重复以上过程。
当webkit创建了renderoject对象后每个对象都是不知道自己的位置,大小等信息的(实际的布局计算在renderobject类中),webkit根据框模型计算他们的位置大小等信息的过程称为布局计算或者排版。布局计算分为两类:第一类是对整个renderobject树进行计算。第二类是对renderobject中某个子树的计算,常见于文本元素活着overflow:auto块的计算,这种情况一般是子树布局的改变不会影响其周围元素的布局,因此不需要计算更大范围内的布局。
布局计算是一个递归的过程,这是因为一个节点的大小通常需要计算他的子女节点的位置大小等信息。步骤如下:
首先,函数(renderobject的layout函数)判断renderobject节点是否需要重新计算。通常需要检查位数组中的相应标记位,子女是否要重新计算等
其次,函数确定网页的宽度和垂直方向上的外边距,这是因为网页通常是在垂直方向上滚动而垂直方向上尽量不需要滚动。
再次,函数会遍历每一个子女节点,依次计算他们的布局。每一个元素会实现自己的layout函数,根据特定的算法来计算该类型元素的布局,如果页面元素定义了自身的宽高。那么webkit按照定义的宽高来确定元素的大小,而对于文字节点这样的内联元素需要结合字号大小和文字的多少来确定对应的宽高。如果页面元素所确定的宽高超出了布局容器包含快所提供的宽高,同时overflow为visible或者auto,webkit会提供滚动条显示所有内容。除非网页定义了页面元素的宽高,一般来说页面元素的宽高实在布局的时候通过计算得到的。如果元素有子女元素那么需要递归这个过程。
最后,节点依据子女们的大小计算的高度得到自己的高度,整个过程结束。那么哪些情况下需要重新计算:
首先,网页首次打开的时候,浏览器设置网页的可是区域,并调用计算布局的方法。这也是一个可见的场景,就是当可视区域发生变化的时候,webkit都需要重新计算布局,这是因为网页块大小发生了变化(rem时候很显然)
其次,网页的动画会触发布局计算,当网页显示结束后动画可能改变样式属性,那么webkit需要重新计算
然后,js代码通过cssom等直接修改样式信息,也会触发webkit重新计算布局
最后,用户的交互也会触发布局计算,如翻滚网页,这会触发新区域布局的计算
注意:布局计算相对比较耗时,一旦布局发生变化,webkit就需要后面的重绘制操作。另一方面,减少样式的变动而依赖现在html5新功能可能有效的提高网页的渲染效率。
4.网页层次和renderlayer树?
网页是可以分层的,原因之一是方便网页开发者开发网页并设置网页的层次,二是为了webkit处理上的便利,也就是说为了简化渲染的逻辑。webkit会为网页的层次创建相应的renderlayer对象。当某些类型的renderobject的节点或者具有某些css样式的renderobject节点出现的时候,webkit就会为这些节点创建renderlayer对象。一般来说,某个renderobject节点的后代的都属于该节点,除非webkit根据规则为某个后代的renderobject节点创建了一个新的renderlayer对象。
注意:renderlayer树时基于renderobject树建立起来的一颗新树,而且renderlayer节点和renderobject节点不是一一对应关系,而是一对多的关系。下面的情况renderobject对象需要建立新的renderlayer节点(而不是独立的图层,注意下面所说的图层是表示在chrome中有黄色的框包围):
(1)It's the root object for the page
(2)It has explicit CSS position properties (relative, absolute or a transform)
(3)It is transparent
(4)Has overflow, an alpha mask or reflection
(5)Has a CSS filter
(6)Corresponds to
下面是《webkit技术内幕的翻译版》:
(1)dom树的document节点对应的renderview节点
(2)dom树中的document的子女节点,也就是html节点对应的renderblock节点
(3)显示的指定css位置的renderobject对象
注意,下面的CSS不会产生一个独立的层(chrome中没有黄色的框包围),但是会产生一个renderLayer对象。
div{
background-color: #ccc;
width:400px;
height:400px;
position: absolute;
left:100px;
top:100px;
transition:width 5s linear;
overflow: hidden;
}
.hv{
width:100px;
}
(4)有透明效果(transparent)的renderobject对象,如果仅仅设置了一个opacity是不会产生一个独立的层的。这里如果是transparent就只会产生一个renderLayer节点,但是这里却会产生一个图层
div{
background-color: #ccc;
width:400px;
height:400px;
opacity: 0.8;
transition:opacity 5s linear;
/*有opacity的变化也会产生一个独立的层,这里的transition可以是all也可以是opacity*/
}
.hv{
opacity: 0;
}
(5)有节点溢出(overflow),alpha或者反射等效果的renderobject对象
(6)使用canvas2d和3d(webgl)技术的renderobject对象
canvas{
background-color: #ccc;
position:absolute;
left:100px;
top:100px;
}
上面这个canvas也不会产生一个独立的图层(但是会产生一个RenderLayer节点),但是如果结合第8点的transform就可以产生图层了
(7)video节点对应的renderobject对象(其他情况参见CSS3硬件加速也有坑!!!)
(8)CSS Transform元素(通过把传递到GPU中的纹理和特定的transform属性结合产生图像就可以了,不需要重绘),这里会产生一个独立的图层,而不仅仅是Renderlayer
如下:
transform: translate3d(0,0,0);
transform:translateZ(0);
transform: scale3d(1,1,1);
transform: scaleZ(1);
transform:rotate3d(0,0,0,0);
transform的scale/translate/rotate的动画也是会产生一个独立的图层的
.container{
background-color: #ccc;
height:100px;
width:100px;
transform:scale(0.5);
transition:transform 5s linear;
/* 这里的scale动画也会产生一个单独的图层,因此不会产生重绘回流等*/
}
.hv{
transform:scale(1);
}
如果仅仅设置一个transform其他的属性是不会产生一个独立的图层的,但是如果是一个动画又会产生一个独立的图层,通过动态的为元素添加下面这个running属性同样会产
生一个独立的图层
.running {
animation: run-around 4s infinite;
}
@keyframes run-around {
0%{
transform: translate(0, 0);
}
25% {
transform: translate(200px, 0);
}
50% {
transform: translate(200px, 200px);
}
75% {
transform: translate(0, 200px);
}
}
3D 和 2D transform 的区别就在于,浏览器在页面渲染前为3D动画创建独立的复合图层,而在运行期间为2D动画创建。动画开始时,生成新的复合图层并加载为GPU的纹理用于初始化 repaint。然后由GPU的复合器操纵整个动画的执行。最后当动画结束时,再次执行 repaint 操作删除复合图层。摘抄自CSS动画之硬件加速
注意:filter属性也不会产生一个独立的层,但是会产生一个RenderLayer对象
那些renderLayer具有独立的后端存储?
To make use of the compositor, some (but not all) of the RenderLayers get their own backing surface (layers with their own backing surfaces are broadly referred to as compositing layers). Each RenderLayer either has its own GraphicsLayer (if it is a compositing layer) or uses the GraphicsLayer of its first ancestor that has one. This is similar to RenderObject’s relationship with RenderLayers.(不是每一个RenderLayer都有自己的后端存储的,如果具有自己独立的GraphicLayer的RenderLayer对象就叫做合成层,合成层的概念见文末) 摘抄自GPU Accelerated Compositing in Chrome
什么样的元素才能创建自己独立的后端存储呢(有了独立的后端存储就表示这个层只有该元素本身,这样对transform/opacity动画是绝好的机会,因为创建了独立的图层而且不需要重绘回流,而只有合成就可以了)?在Chrome中至少要符合以下条件之一:
Layer has 3D or perspective transform CSS properties(有3D元素的属性)
Layer is used by
div {
min-height: 380px;
margin-bottom: 20px;
background-image: url('./woman.jpg');
background-size: cover;
background-repeat: no-repeat;
filter: blur(60px) saturate(120%) brightness(140%);
-webkit-tap-highlight-color: rgba(0,0,0,0);/*这个仅仅使用filter是不会产生一个独立的层的,除非结合translate3d等*/
}
Layer with a composited descendant has information that needs to be in the composited layer tree, such as a clip or reflection(有一个后代元素是独立的layer)
Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer is rendered on top of a composited layer)(元素的相邻元素是独立layer)
以上内容来自:Javascript高性能动画与页面渲染
下面是renderobject树和renderlayer树对应关系:
注意:上图renderlayer树应该包含三个renderlayer节点-根节点,子女节点以及叶子节点
除了跟节点,也就是renderlayer节点,一个renderlayer节点的父亲就是该renderlayer节点对应的renderobject节点的祖先链中最近的祖先,并且祖先所在的renderlayer节点同该节点的renderlayer节点不同。基于这个原理,这些renderlayer节点也就构成了一个renderlayer树。每个renderlayer节点包含的renderobject节点都是一个renderobject子树,理想情况下,每个renderlayer对象都有一个后端类,这个后端类涌来存储改renderlayer对象的绘制结果。renderlayer节点可以有效的减少网页的复杂程度,并且在很多情况下能够减少页面重新渲染的开销。注意:renderlayer没有子类,但是renderobject有子类的概念,renderlayer只是表示网页的一个层次,没有子层次的概念。
这个例子就会创建四个层,如下图:
所谓的根层就是跟节点创建的层,它对应着整个网页的文档对象。video对象创建一个层可以有效的处理视频解码器和浏览器之间的交互和渲染。同时需要3d转换的元素也会创建相应的层。位置上根层在最后,层3和层4在最前面。webkit创建新层实际上是为了渲染引擎处理上的方便和高效。下面的例子也会创建两个层:
I am a strange root.
通常,Chrome 会
将一个层的内容在作为纹理上传到 GPU 前先绘制(paint)进一个位图中。如果内容不会改变,那么就没有必要重绘(repaint)。这样处理很好:花在重绘上的时间可以用来做别的事情,例如运行 JavaScript,如果绘制的时间很长,还会造成动画的故障与延迟。Chrome 并不会始终重绘整个层,它会尝试智能的去重绘 DOM 中失效的部分。在本例中,我们修改的 DOM 元素和整个层同样大小。但是在其他众多例子中,一个层内会存在多个 DOM 元素。至于为什么是后面的层更加靠近观察者,请仔细阅读 深入理解CSS中的层叠上下文和层叠顺序
5.webkit的渲染过程?
第一阶段:从网页的url到构建完dom树
具体示意图如下:
注意:网页在加载和渲染过程中会发出domcontent事件和dom的onload事件,分别在dom树构建完成以及dom树构建完并且网页所依赖的资源都加载完成之后。
具体过程如下:
1.网页输入URL时候,webkit调用其资源加载器(总共有三类:特定资源加载器如imageloader,资源缓存机制的资源加载器如cachedresourceloader,通用资源加载器resourceloader)加载该URL对应的网页
2.加载器依赖网页模块建立连接,发起请求并接受回复
3.webkit接受到各种网页或者资源的数据,其中某些资源可能是异步的或者同步的
4.网页被加载给html解释器变成一系列的词语(token)
5.解析器根据词语构建节点node,形成dom树
6.如果节点是js代码的话,调用js引擎解释并执行
7.js代码可能会修改dom树的结构
8.如果节点需要依赖其他资源,例如图片,css,视频等,调用资源加载器加载他们,但是他们是异步的,不会阻碍当前dom树的构建。如果是js资源,那么需要停止当前dom树的构建,直到js资源加载并将被js引擎执行后才继续dom树的构建。我们看看html解析器的解析过程:
第二阶段:从dom树到构建完webkit绘图上下文(webkit利用css和dom树构建renderobject树直到绘图上下文)。具体过程如下:
注意:renderobject树的建立并不表示dom树被销毁,事实上上面四个内部表示结构一直存在,直到网页被销毁,因为他们对于网页的渲染起了很大的作用
1.css文件被css解析器解释成为内部表示结构
2.css解析器工作完成之后,在dom树上附加解释后的样式信息,这就是renderobject树
3.renderobject节点在创建的同时,webkit会根据网页的层次结构创建renderlayer树,同时构建一个虚拟的绘图上下文
第三阶段:从绘图上下文到最终的图像
这一过程主要依赖于2d和3d图像库,具体过程如下:
1.绘图上下文时一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类
2.绘图实现类也可能有简单的实现,也可能有复杂的实现,在chromium中,他的实现相当复杂,需要chromium合成器来完成复杂的多进程和gpu加速
3.绘图实现类将2d图形库和3d图形库绘制的结果保存下来,交给浏览器来同浏览器界面一起显示
总结:现在的网页很多是动态的网页,这意味着在渲染完成之后,由于网页的动画或者用户的交互,浏览器其实一直在不停的重复执行渲染过程
6.chrome控制台浏览器的加载过程
我们试试navigation.timing
navigationStart:
当load/unload动作被触发时,也可能是提示关闭当前文档时(即回车键在url地址栏中按下,页面被再次刷新,submit按钮被点击)。如果当前窗口中没有前一个文档,
那么navigationStart的值就是fetchStart。
redirectStart:
它可能是页面重定向时的开始时间(如果存在重定向的话)或者是0。
unloadEventStart:
如果被请求的文档来自于前一个同源(同源策略)的文档,那么该属性存储的是浏览器开始卸载前一个文档的时刻。否则的话(前一个文档非同源或者没有前一个文档)
,为0。
unloadEventEnd:
表示同源的前一个文档卸载完成的时刻。如果前一个文档不存在或者非同源,则为0。
redirectEnd:
如果存在重定向的话,redirectEnd表示最后一次重定向后服务器端response的数据被接收完毕的时间。否则的话就是0。
fetchStart:
fetchStart是指在浏览器发起任何请求之前的时间值。在fetchStart和domainLookupStart之间,浏览器会检查当前文档的缓存。
domainLookupStart:
这个属性是指当浏览器开始检查当前域名的DNS之前的那一时刻。如果因为任何原因没有去检查DNS(即浏览器使用了缓存,持久连接,或者本地资源),
那么它的值等同于fetchStart。
domainLookupEnd:
指浏览器完成DNS检查时的时间。如果DNS没有被检查,那么它的值等同于fetchStart。
connectStart:
当浏览器开始于服务器连接时的时间。如果资源取自缓存(或者服务器由于其他任何原因没有建立连接,例如持久连接),那么它的值等同于domainLookupEnd。
connectEnd:
当浏览器端完成与服务器端建立连接的时刻。如果没有建立连接它的值等同于domainLookupEnd。
secureConnectionStart:
可选。如果页面使用HTTPS,它的值是安全连接握手之前的时刻。如果该属性不可用,则返回undefined。如果该属性可用,但没有使用HTTPS,则返回0。
responseStart:
指客户端收到从服务器端(或缓存、本地资源)响应回的第一个字节的数据的时刻。
responseEnd:
指客户端收到从服务器端(或缓存、本地资源)响应回的最后一个字节的数据的时刻。
domLoading:
指document对象创建完成的时刻。
domInteractive:
指文档解析完成的时刻,包括在“传统模式”下被阻塞的通过script标签加载的内容(除了使用defer或者async属性异步加载的情况)。
domContentLoadedEventStart:
当DOMContentLoaded事件触发之前,浏览器完成所有script(包括设置了defer属性但未设置async属性的script)的下载和解析之后的时刻。
domContentLoadedEventEnd:
当DOMContentLoaded事件完成之后的时刻。它也是javascript类库中DOMready事件触发的时刻。
domComplete:
如果已经没有任何延迟加载的事件(所有图片的加载)阻止load事件发生,那么该时刻将会将document.readyState属性设置为"complete",此时刻就是
domComplete。
loadEventStart:
该属性返回的是load事件刚刚发生的时刻,如果load事件还没有发生,则返回0。
loadEventEnd:
该属性返回load事件完成之后的时刻。如果load事件未发生,则返回0。
检测用户通过哪种方式来到此页面:
我们有几种方式来打开一个页面,例如,在地址栏输入url,刷新当前页面,通过history的前进后退。这时候 performance.navigation 就派上用场了。这个 API 有
两个属性:
以下列举了 type 属性的三种取值情况:
7.webkit渲染方式?
在构建完了dom树之后,webkit所要做的事情就是构建渲染的内部表达并使用图形库将这些模型绘制出来。网页的渲染方式有两种,第一种是软件渲染,
第二种是硬件加速渲染。每一个层对应于网页中的一个或者一些可视元素,这些元素绘制内容到这个层中。如果绘图操作使用CPU来完成就叫做软件绘图,
如果绘图操作使用gpu来完成,那么就叫做gpu硬件加速绘图。理想情况下每一个层都有一个绘制的存储区域,这个存储区域用于保存绘图的结果。
最后需要把这些层的内容合并到同一个图像之中,叫做合成(compositing)。使用了合成技术的渲染称之为合成化渲染。
在renderobject树和renderlayer树之后,webkit的内部操作将内部模型转化为可视结果分为两个阶段:每层的内容进行绘图工作以及之后将这些绘图的结果合并为一个图像。
如果对于软件渲染,那么需要使用CPU来绘制每一层的内容,但是他是没有合成阶段的,因为在软件渲染中,渲染的结果就是一个位图,绘制每一层的时候都使用这个位图,
区别在于绘制的位置可能不一样,当然每一层都是按照从后到前的顺序。当然你也可以为每一层分配一个位图,但是一个位图已经可以解决所有的问题了。
下面是网页的三种渲染方式:
软件渲染中网页使用的一个位图,实际上是一块CPU使用内存。第二种和第三种方式都是使用了合成化的渲染技术,也就是使用gpu硬件加速来合成这些网页,合成的技术
都是使用gpu来做的,所以叫做硬件加速。但是,对于每一个层这两种方式有不同的选择。如第二种方式,某些层使用gpu而某些层使用CPU,对于CPU绘制的层,该层
的结果首先当然保存在CPU内存中,之后被传输到gpu的内存中,这主要是为了后面的合成工作。第三种方式使用gpu来绘制所有的合成层。第二种和第三种方式都属于
硬件加速渲染方式。
那么上面三种绘图的区别:
首先,对于常见的2d绘图操作,使用gpu来绘图不一定比CPU绘图在性能上有优势,因为CPU的使用缓存机制有效减少了重复绘制的开销而且不需要gpu并行性。其次,
gpu的内存资源相对于CPU的内存资源来说比较紧张,而且网页的分层似的gpu的内存使用相对比较多。
软件渲染:浏览器最早的渲染机制,比较节省内存特别是宝贵的gpu内存,但是软件渲染只能处理2d方面的操作。简单的网页没有复杂绘图或者多媒体方面的需求,
软件渲染就适合处理这种类型的网页。但是如果遇到html5新技术那么软件渲染就无能为力,一是因为能力不足,如css3d,webGL;二是因为性能不好,如canvas2d和视频。
因此软件渲染被用的越来越少,特别是移动领域。软件渲染和硬件加速渲染另外一个很不同的地方在于对更新区域的处理,当网页有一个更小型区域的请求如动画时,软件
渲染只要计算极小的区域,而硬件渲染可能需要重绘其中的一层或者多层,然后再合成这些层,硬件渲染的代价可能大得多。
硬件加速的合成:每一个层的绘制和所有层的合成均使用gpu硬件来完成,这对需要使用3d绘图的操作来说特别合适。在这种方式下,在renderlayer树之后,
webkit和chromium还需要建立更多的内部表示,例如graphiclayer(renderlayer中前景和背景层需要的一个后端存储,因为只有 GraphicsLayer 是作为纹理(texture)上
传给 GPU 的)树,合成器中的层(如chromium的cclayer等),目的是支持硬件加速,这显然会消耗更多的内存资源。但是,一方面,硬件加速能够支持现在所有的回
html5定义的2d或者3d绘图标准;另外一方面,关于更新区域的讨论,如果需要更新某个层的一个区域,因为软件渲染没有为每一层提供后端存储,因而它需要将和这个区域
有重叠部分的所有的层次相关区域依次向后向前重新绘制一遍,而硬件加速渲染只是需要重新绘制更新发生的层次,因而在某些情况下,软件渲染的代价更大,当然,
这取决于网页的结构和渲染策略。
软件绘图的合成化渲染方式结合了前面两中方式的优点,这是因为很多网页可能既包含了基本的html5元素也包含html5新功能,使用CPU绘图方式来绘制某些层,
使用gpu绘图方式来绘制其他一些层。原因是前面所说的性能和内存综合考虑。
8.webkit软件渲染技术
很多情况下,也就是没有哪些硬件加速内容的时候(css3变形,变换,webgl,视频),webkit可以使用软件渲染来完成页面的绘制工作。软件渲染需要关注两个方面,
分别是renderlayer树和renderlayer树包含的renderobject树:
webkit如何遍历renderlayer树来绘制每一个层?
对于每一个renderobject对象,需要三个阶段绘制自己。第一阶段:绘制该层中的所有块的背景和边框 第二阶段:绘制浮动内容 第三阶段:前景也就是内容部分,
轮廓等部分。注意:内联元素的背景,边框,前景都是在第三阶段被绘制的,这是不同之处。注意:在最开始的时候,也就是webkit第一次绘制网页的时候,webkit绘制的
区域等同于可视区域的大小,而在这之后,webkit只是首先计算需要更新的区域,然后绘制同这些区域有交集的renderobject节点。这也就是说,如果更新区域跟某个
renderlayer节点有交集,webkit会继续查找renderlayer树中包含derenderobject子树中的特定的一个或者一些节点而不是绘制整个renderlayer对应的rendeobject子树。
webkit软件渲染结果的存储方式,在不同的平台上可能不一样,但是基本上都是CPU内存的一块区域,多数情况下是一个位图。至于这个位图如何处理,如何和之前绘
制的结果进行合并,如何显示出来,都和webkit的不同移植有关。
9.webkit硬件加速
对于gpu绘图而言,通常不像软件渲染那样知识计算其中更新的区域,一旦有更新请求,如果没有分层,引擎可能需要重绘所有的区域。因为计算更新部分对gpu来说可
能耗费更多的时间,当网页分层之后部分区域的更新可能只在网页的一层或者几层,而不需要把整个网页进行重绘。通过重新绘制网页的一层或者几层,将他们和其他
之前绘制完的层合并起来,既能使用gpu的能力,又能够减少重绘的开销。理想情况下,每一个renderlayer都有一个后端存储(renderlayerbacking对象),但是实
际上都是不一样的,主要原因在于实际中的硬件能力和资源有限,为了节省gpu内存资源,硬件加速机制在renderlayer树建立之后需要做三件事情来完成网页的渲染:
第一:webkit决定将哪些renderlayer对象组合在一起,形成一个有后端存储的新层,整个新层不久后用于之后的合成,这里称为合成层(compositing layer)。每一
个新层都有一个或者多个后端存储,这里的后端存储可能是gpu内存。对于一个renderlayer对象,如果它没有后端存储的新层,那么就使用其父亲所使用的合成层。
第二:将每个合成层包含的这些renderlayer内容绘制在合成层的后端存储中,这里的绘制可能是软件绘制也可能是硬件绘制
第三:由合成器将多个合成层合成起来,形成网页最终可视化结果,实际上就是一张图片。合成器是一个能够将多个合成层按照这些层的先后顺序,合成层的3d变形等
设置二合成一个图像结果的设施。如果一个renderlayer对象具有以下特征之一那么就是合成层:
(1)renderlayer具有css3d属性活着css透视效果
(2)renderlayer包含的renderobject节点表示的是使用硬件加速的视频解码技术的html5的video元素
(3)renderlayer包含的renderobject节点表示的是使用硬件加速的canvas2d元素或者webgl技术
(4)renderlayer使用了css透明效果的动画或者css变换的动画
(5)renderlayer使用了硬件加速的css filters技术
(6)renderlayer使用了剪裁(clip)或者反射(reflection)属性,并且他的后代中包含了一个合成层
(6.1)Layer has a descendant that is a compositing layer 。C
omposited descendant may need composited parent,为了在composited tree中正确的传播
transform,perserve-3d或者clipping information。如下面的例子:
body {
text-align: center;
padding: 30px;
}
.camera{
-webkit-perspective:100;
perspective:1000;
background-color:#ccc;
width:100px;
height: 100px;
}
.space{
-webkit-transform-style:preserve-3d;
border:1px solid black;
width:100%;
height:80%;
}
/*box肯定会在一个独立的图层中进行渲染,space也会,camera也会,即使给我们*/
.box{
animation:x-spin 5s infinite;
-webkit-animation:x-spin 5s infinite; /* Safari 和 Chrome */
}
@keyframes x-spin{
0% {
-webkit-transform: rotateX(0deg);
}
50% {
-webkit-transform: rotateX(180deg);
}
100% {
-webkit-transform: rotateX(360deg);
}
}
DOM结构如下:
注意:此时camera,space,box都会生成独立的图层。而且因为camera使用了perspective产生了层叠上下文,所以其z-index级别相当于auto/0的级别。你可以运行一下
这个例子来体会一下。同时我要告诉你,如果使用了translateZ来产生复合图层,那么父元素不会放在一个独立的复合图层中。
(6.2)will-change:transform和will-change:opacity也会产生一个复合图层,如果设置为auto无效
(7)renderlayer有一个z坐标(其实就是堆叠上下文)比自己小的兄弟节点,并且该节点是一个合成层。((in other words the layer overlaps a composited layer and
should be rendered
on top of it))
主要原因在于:浏览器不知道当前具有较大的z-index的兄弟节点是否和合成层具有重叠的部分,必须创建一个合成层用于维持次序。
我们来分析一下第7中情况,如下图:
这时候假如所有的元素都在一个renderLayerBacking对象中进行绘制。但是我们通过一定的方式为蓝色框创建一个合成层(可能产生堆叠上下文进而在z轴上具有较大的值),
于是成为下面这种情况
这时候我们的绿色部分已经在蓝色部分下面的,这显然不是我们需要的(不能因为给元素创建了一个合成层进而影响到元素在Z轴上的顺序),因此chrome会自动为应该
处于
上层的元素创建
一个合成层:
这就是我们说的第7中情况,这种情况是因为我们的图层和一个合成层存在重叠,因此也会被创建为一个合成层。我们再来看一个例子:
通过 这个例子我们可以看到:首先我们的滚动条被绘制到一个单独的合成层中,因此他具有独立的后端存储,在变化的时候不需要进行重绘和回流操作;然后,我们看看
DOM结构
Poster Circle
This is a simple example of how to use CSS transformation and animations to get interesting-looking behavior.
The three rings are constructed using a simple JavaScript function that creates elements and assigns them a transform
that describes their position in the ring. CSS animations are then used to rotate each ring, and to spin the containing
element around too.
Note that you can still select the numbers on the ring; everything remains clickable.
我们的ring-1,ring-2,ring-3都是在一个独立的合成层(
上面的6.1说明了rotate,stage都会产生一个独立的图层,而且其堆叠上下文是auto/0级别),但是p元素没有在一个独立的
合成层当中。但是,问题来了,当我们给我们的p元素添加一个z-index:1;position:relative我们很明显的看到chrome会为我们的p元素也创建一个独立的合成层,但是如果
我把p元素的z-index设置为0,那么创建的图层又会消失。这就是我们上面说的第七种情况,也就是一个元素具有z-index比自己小(auto/0级别),同时还是合成层的元素,
那么chrome也会为当前元素创建合成层。最后,
我们再来看一下我们上面标红的情况,也就是z坐标的比较(建议先读 这篇文章来了解堆叠上下文的概念):
堆叠上下文:堆叠上下文会flatten元素的subtree,就是在这个子树之外的任何元素都不会再子树之间绘制。也就是说:
DOM树中,我们可以把堆叠上下文作为一个原子
概念层
(atomic conceptual layer)用于绘制。因此,堆叠上下文是一个用于定义绘制顺序的绝好概念(建议仔细阅读 深入理解CSS中的层叠上下文和层叠顺序)
【堆叠上下文的绘制次序】:背景和边框,其次是负数z-index的节点,然后是正常文档流中的元素,接着是z-index为0和绝对定位的子元素,最后是正的z-index的元素。
这就是出现相互覆盖的原因。
【何时选择复合层】
(1)当renderLayer子树被缓存和重组过后能够提升性能的情况下是可以的。
(2) opacity,transform,filters,reflections:当drawing的时候非常容易被用于合成层,因为此时只是需要GPU处理,而不用重绘和回流
(3)scrolling,fixed-position:当合成一个subtree内容的时候如果可以极大的减少重绘的数量的时候可以
(4)content that is rendered separately:在GPU中合成能够减少反复读取像素,例如WebGl,硬件解码video
【图层挤压】
GraphicLayer非常消耗内存和资源,如果一个层和具有独立后端存储的层存在重叠的部分,那么这个层也会被创建一个独立的后端存储。我们把一些如具有3D转换的层叫做
‘直接的’合成层,为了防止当很多在顶部(注意是顶部)的同时具有独立后端存储的层的存在而导致的‘层爆炸’问题,Blink就会把那些和‘直接’合成层重叠的层挤压到一个唯一的
后端存储中,这就解决了层级爆炸问题。GPU Accelerated Compositing in Chrome,如果动画运行的时候创建了很多的层,这时候应该是动画所在的层级比较低,所以
可以把动
画的层级提升就可以了
【选择合成层的好处】
首先当然是合并一些renderlayer层,可以减少内存的使用量;其二是在合并之后,尽量减少合并带来的重绘性能和处理上的困难;其三,对于那些使用单独层能够显著提
升性能的renderlayer对象可以继续使用这个好处。如webgl技术的canvas元素。下面是renderlayer,renderlayerbacking,graphiclayer的对应关系:
请继续阅读 GPU硬件加速的那些优秀的资源总结-续