GPU硬件加速的那些优秀的资源总结-续

renderlayerbacking如下:


注:renderlayerbacking负责管理renderlayer所需要的所有的后端存储,因为后端存储可能需要多个存储空间,在webkit中,存储空间使用graphiclayer来表示。

那么为什么一个renderlayerbacking对象需要那么多层?

原因有很多,例如webkit需要将滚动条独立开来称为一个层,需要两个容器层来表示renderlayer对应的z坐标为正数和z坐标为负数的子女,需要滚动的内容建立新层,还可能需要剪裁层和反射层,这些层绘制和组织的顺序如下图(图中每一个层就是一个graphiclayer对象):

       

对于每一个renderlayerbacking对象来说,其主层肯定是存在的,其它层不一定存在,因为不是每一个renderlayer对象都需要用到他们。管理这些层的工作是renderlayercompo sitor类,这个类可以说是大管家,它不仅决定哪些renderlayer对象是合成层,而且为合成层创建graphiclayer对象。每个renderview对象包含一个renderlayercompositor,这个对象仅在硬件加速机制下才会被创建。renderlayercompositor类本身也类似于一个renderlayerbacking,也就是说它也包含一些graphiclayer对象,这些对象对应于整个网页所需要的后端存储。

在chromium中,所有使用gpu硬件加速的操作都是由一个进程也就是gpu进程来完成的,这其中包括使用gpu硬件来进行绘图和合成。chromium时多进程的,每一个网页的render进程都是将之前介绍的3d绘图和合成操作通过ipc传递给gpu进程,由他来统一调度和执行。事实上每一个render进程都依赖gpu进程来渲染网页,当然browser进程也会同gpu进程进行通信,其作用是创建该进程并提供网页渲染过程最后绘制的目标存储


注意:webkit定义了两种类型的图形上下文,他们都可以使用gpu来加速,加速机制最后都是通过调用opengl/opengles库。


3d,2d图形上下文在chromium中分别对应chromium的3d图形上下文实现喝skia画布(canvas),他们在调用之后会被转换成ipc消息给gpu进程,该进程中的解释器对这些消息进行解释后,调用gl函数指针表中的函数,这些函数指针是从gl库中获取的,这样设计会更加灵活。在window平台上3d图形库时d3d而不是opengl。

注意:gpu进程在处理一些命令后会向render进程报告自己的当前状态,render进程通过检查状态信息和自己的期望结果来确定是否满足自己的条件。gpu进程最终绘制的的结果不再像软件渲染那样通过共享内存传递给browser进程,而是直接将页面的内容绘制在浏览器的标签窗口内。

9.chromium合成器

合成器的作用是将多个合成层合成并输出一个最终的结果,所以他的输入时多个待合成的合成层,每个层都有一些属性,如3d变形等,他的输出时一个后端存储,例如一个gpu的纹理缓冲区。后端存储使用了瓦片状的存储:


为什么使用瓦片状的后端存储?

第一:dom树中html元素所在的层可能比较大,因为网页的高度很大,如果只是使用一个后端存储的话,那么需要一个很大的纹理对象,但是实际的gpu硬件可能只支持非常有限的纹理;第二,在一个比较大的合成层中,可能只是其中一部分发生变化,如果需要绘制整个层必然会产生额外的开销,使用瓦片的后端存储就只需要绘制一些存在更新的瓦片;第三,当层发生滚动的时候,一些瓦片可能不再需要,然后webkit需要一些新的瓦片来绘制新的区域,这些大小相同的后端存储很容易重复利用。

10.chromium合成过程

合成工作主要分为四步:

首先,创建输出结果的目标对象‘Surface’,也就是合成结果的存储空间

然后,开始一个新的帧,包括计算滚动和缩放大小,动画计算,重新计算网页的布局,绘制每一个合成层等

然后,将layer树种包含的这些变动同步到layerImpl树中

最后,合成LayerImp树中的各个层并交换前后帧缓冲区,完成一帧的绘制和显示动作

上面的步骤一是最开始的时间调用的,而且是一次性的动作。当后面出现网页动画或者js代码修改CSS样式或者DOM等情况的时候,一般会执行后面三个步骤,当然也可能只要修改步骤4.


在上图中,Compositor线程首先创建合成器需要的输出结果的后端存储,在调度器执行该任务时候,该线程会将任务交给主线程来完成。主线程会创建后端存储并把它传回Compositor线程

第二步:Compositor线程告诉主线程需要开始绘制一个新的帧,通常是通过线程间通信来传递任务。当主线程收到该任务的时候,需要做的事情非常多。如执行动画操作,重新计算布局,以及绘制需要更新的合成层。在这之后,主线程会等待第三个步骤,当第三个步骤完成后,他通知主线程的LayerHost等类,这是因为步骤三需要阻塞主线程,需要同步Layer树

第三步:如下图



首先,当Layer树有变动的时候,他需要调用setNeedsCommit,这些任务是在渲染线程中的,随后他会提交到一个请求到Compositor线程

其次,当该Compositor线程处理到该请求的时候,他会通过调度器的setNeedsCommit函数设置状态机的状态

再次,调度器的setNeedsCommit会调用ProcessScheduleActions函数,他检查后面需要执行的任务

然后,如果没有其他任务或者时间合适的话,状态机决定下面立刻执行该任务,他调用ThreadProxy的ScheduledActionCommit函数,该函数实际执行commit任务需要的具体流程

最后,在ScheduledActionCommit函数中,他会调用LayerTreeHostImpl和LayerTreeHost中的相应函数来完成同步两个树的工作,同步结束后他需要通知渲染线程,因为事实上这一过程需要阻止该线程。

第四步:主要就是合成工作了。经过第三步后,Compositor实际上已经不再需要主线程的参与就能够完成合成工作了,这时候该线程有了需要合成这些层的所有资源。途中5.1.1.到5.1.6这些子步骤就是合成各个层并交换前后缓冲区,这些过程是不需要主线程的参与的。这样就能够解释渲染线程在做其他事情的时候,网页滚动等操作并不会受到渲染线程的影响,因为这时候合成器的工作线程仍然能够正常进行,合成器线程继续合成当前的各个合成层生成网页结果,虽然此时可能有些内容还没有更新,但是用户根本感觉不到网页被阻塞等问题,浏览器网页的用户体验更好

chromium的最新设计为了合成网页和浏览器的用户界面可能需要多个合成器。每个网页可能需要一个合成器,网页的iframe可能也需要一个合成器,整个网页和浏览器的合成也需要一个合成器,这些合成器构成一个层次化的合成器结构。


上图中的根合成器是浏览器最高的合成器,该合成器负责网页和浏览器用户界面的合成。他有一个子女就是合成器2,根合成器会将合成器2的结果同用户界面合成起来,合成器2就是网页的合成器,而他也包含一个合成iframe内容的合成器3子合成器。这里,按理来说,合成器2/3是在renderer进程中进行的,因为他们是网页相关的合成,而根合成器是在Browser进程中的,这样会增加内存带宽的使用。目前Chromium设计使用mailBox机制将renderer进程中的合成器同步到Browser进程,根合成器可以使用这些结果

11.从 DOM 到屏幕
那么 Chrome 是如何将 DOM 转变成一个屏幕图像的呢(可以阅读Accelerated Rendering in Chrome)?从概念上讲,它:
(1)获取 DOM 并将其分割为多个层
(2)将每个层独立的绘制进位图中
(3)将层作为纹理上传至 GPU

       纹理:可以把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmap image)。注意:通常,Chrome 会将一个层的内容在作为纹理上传到 GPU 前先绘制(paint)进一个位图中。如果内容不会改变,那么就没有必要重绘(repaint)。这样处理很好:花在重绘上的时间可以用来做别的事情,例如运行 JavaScript,如果绘制的时间很长,还会造成动画的故障与延迟。
(4)复合多个层来生成最终的屏幕图像。
当 Chrome 首次为一个 web 页面创建一个帧(frame)时,以上步骤都需要执行。但对于以后出现的帧可以走些捷径:
如果某些特定 CSS 属性变化,并不需要发生重绘。Chrome 可以使用早已作为纹理而存在于 GPU 中的层来重新复合,但会使用不同的复合属性(例如,出现在不同的位置,拥有不同的透明度opacity等等)。点击前端性能优化(CSS动画篇)
如果层的部分失效,它会被重绘并且重新上传。如果它的内容保持不变但是复合属性发生变化(例如,层被转化或透明度发生变化),Chrome 可以让层保留在 GPU 中,并通过重新复合来生成一个新的帧。
现在你应该清楚了,以层为基础的复合模型对渲染性能有着深远的影响。当不需要绘制时,复合操作的开销可以忽略不计,因此在试着调试渲染性能问题时,首要目标就是要避免层的重绘。精明的程序员可能在看上面介绍的复合触发列表时意识到可以轻而易举的控制层的创建。但要注意不要盲目的创建层,因为它们并不是毫无开销:层会占用系统 RAM 与 GPU(在移动设备上尤其有限)的内存,并且拥有大量的层会因为记录哪些是可见的而引入额外的开销。许多层还会因为过大与许多内容重叠而导致“过度绘制(overdraw)”的情况发生,从而增加栅格化的时间。所以,谨慎的利用你所学到的知识!

12.减少网页重绘

网页加载后,每当重新绘制新的一帧的时候,一般需要三个阶段,也就是前面说的计算布局,绘图,合成。如果想减少每一帧的时间,提升性能,当然要减少这三个阶段的时间。
在这三个阶段中,前面两个阶段时间较长,而合成需要的时间要少一些。而且,当布局变化越多,webkit通常需要越多的绘图时间。例如当使用JS计时器来控制动画的时候,webkit可能需要修改布局和比较多的绘图操作,这会明显增加webkit绘制每一帧的时间,那么如何避免这一种情况呢?
方法一:使用合适的网页分层技术减少需要重新计算的布局和绘图
例如一个闯关的HTML5游戏,使用多个canvas要比使用单个canvas要好得多。每一个canvas元素都是一个合成层,每一帧的变化都只是一个或者部分合成层,而不是所有的canvas元素。
方法二:使用CSS3D变形和动画技术

他能够让浏览器仅仅使用合成器来合成所有的层就能够达到动画效果,而不是通过重新设置其他CSS属性并触发计算布局,重新绘制图形,重新合成所有的层这一个复杂的过程。实际上,开发者如果需要网页中有一些动画或者特殊效果,可以给这些元素设置3D变形属性,然后通过CSS3引入的动画能力,网页就可以达到很多匪夷所思的效果。更重要的是,webkit不需要大量的布局计算,不需要重新绘制,只需要修改合成时候的属性就可以了。当合成器需要合成的时候,每个合成层都可以设置自己的3D变形属性,这些属性仅仅改变合成层的变换参数,而不需要布局计算和绘图操作,这就可以极大的节省时间。

在famo.us网站上,我们可以看到:在计算每一帧的时候JS代码首先设置元素的3D属性,然后设置样式信息,但是浏览器不需要重新布局,也不需要重新绘图,只是在随后使用合成功能,而合成功能的时间非常少,几乎可以忽略不计,这使得计算每一帧都相对比较省时,因为每一帧的生成没有了费时的布局计算和绘图操作。
2D图形的硬件加速机制:
网页中很多操作都是针对2D图形的,这些操作包括通常的网页绘制,如绘制边框,文字等。HTML5中又引入了2D绘图的画布功能,2D绘图本身是使用2D的图形上线文,而且一般使用软件方式来绘制他们,也就是光栅化(Rasterize)的方法。但是这些2D绘图操作也可以是用GPU也就是3D绘图来完成,这里吧使用GPU来绘制2D图形的方法称为2D图形的硬件加速机制。目前2D图形的硬件加速有两种应用场景:
第一种就是网页基本元素的绘制,针对的层次类型就是ContentLayer,后端是一个2D的画布对象。
     webkit的2D图形上下文,该上下文在webkit是使用Skia图形库来完成2D图形操作。至于SkCanvas对象是使用软件绘图还是GPU绘图取决于SkCanvas对象的设置。
第二种就是HTML5的canvas,用来绘制2D图形。
Khronous组织提出可以在canvas元素上使用JS接口绘制3D图形,这种技术我们称为WebGL或者canvas3D。一个canvas只能绘制2D或者3D图形的一种,不能同时绘制。

13.动画性能优化的其他资源

当我们通过某些行为(点击、移动或滚动)触发页面进行大面积绘制的时候,浏览器往往是没有准备的,只能被动使用CPU去计算与重绘,由于没有事先准备,应付渲染够呛,于是掉帧,于是卡顿。而will-change则是真正的行为触发之前告诉浏览器:“浏览器同学,我待会儿就要变形了,你心理和生理上都准备准备”。于是乎,浏览器同学把GPU给拉上了,从容应对即将到来的变形。摘抄自:使用CSS3 will-change提高页面滚动、动画等渲染性能

60Hz和60fps有什么关系?没有任何关系。fps代表GPU渲染画面的频率,Hz代表显示器刷新屏幕的频率。一幅静态图片,你可以说这副图片的fps是0帧/秒,但绝对不能说此时屏幕的刷新率是0Hz,也就是说刷新率不随图像内容的变化而变化。游戏也好浏览器也好,我们谈到掉帧,是指GPU渲染画面频率降低。比如跌落到30fps甚至20fps,但因为视觉暂留原理,我们看到的画面仍然是运动和连贯的。问题产生于GPU渲染画面的频率和屏幕刷新频率的不一致:如果GPU渲染出一帧画面的时间比显示器刷新一张画面的时间要短(更快),那么当显示器还没有刷新完一张图片时,GPU渲染出的另一张图片已经送达并覆盖了前一张,导致屏幕上画面的撕裂,也就是是上半部分是前一张图片,下半部分是后一张图片PC游戏中解决这个问题的方法是开启垂直同步(v-sync),也就是让GPU妥协,GPU渲染图片必须在屏幕两次刷新之间,且必须等待屏幕发出的垂直同步信号。但这样同样也是要付出代价的:降低了GPU的输出频率,也就降低了画面的帧数。以至于你在玩需要高帧数运行的游戏时(比如竞速、第一人称射击)感觉到“顿卡”,因为掉帧。

如果你的回调函数耗时真的很严重,rAF还是可以为你做一些什么的。比如当它发现无法维持60fps的频率时,它会把频率降低到30fps,至少能够保持帧数的稳定,保持动画的连贯。:摘自Javascript高性能动画与页面渲染

14.触发硬件加速的那些属性

transform;opacity;filter

15.为什么不为每一个元素创建一个独立的图层

首先为每一个元素都创建一个图层会非常消耗内存;同时从CPU上传纹理到GPU中会有带宽的限制;而且开启GPU加速也会消耗大量的电量。

你可能感兴趣的:(GPU硬件加速的那些优秀的资源总结-续)