浏览器的渲染

渲染流程

浏览器的渲染_第1张图片图一:软件渲染

如上图所示,首先浏览器把Html、CSS解析成Dom Tree和Style Rules,并把两者做关联,这样我们便可以很方便的通过JS去操作他们。

但Dom Tree中有太多的东西并不是显示相关的,比如事件的监听、创建删除操作等,为了方便浏览器绘图,将Dom中显示相关的属性做提取,并对一些布局相关的属性(float、inline)做计算,最后形成一棵Render Tree,Render Tree描述了每个节点的位置、大小、深度等绘图需要的基础信息,有了这些信息,接下来的绘图工作就方便了很多。

使用Chrome控制台的TimeLine,我们可以很清楚的看到这些流程
浏览器的渲染_第2张图片

顺便说一下:

  • Painting之前的流程全部由CPU完成,无论是软件渲染还是硬件渲染。
  • 为了更方便的做图层管理,还会再抽象一层Render Layer Tree。

软件渲染

现在用的不多,主要是因为GPU的普及,不过还是很有必要介绍下的。

多线程绘图优化

再看下图一,一个渲染的过程有很多的步骤,如果都由一个线程完成,很难保证不卡顿,于是浏览器将图片解码、绘图的操作从主线程移到了其他线程。
如果绘图出现卡顿,不会影响主线程的业务逻辑,只会出现显示上的跳帧。
浏览器的渲染_第3张图片

开始绘制

此时我们已经有一个Render Tree了,接下来按照Render Layer 的层次,在一个绘图上下文(可以形象的理解成一块画布),先画底层元素,再画上层元素,最终将所有元素绘制在一张位图中。

浏览器的渲染_第4张图片

渲染好后,通知浏览器进程,浏览器进程调用系统的GDI接口将图像输出到最终的显示设备上。

Shared memory中的缓存的好处:当界面有滚动条滚动时,Shared memory中的缓存不需要重绘,只需要重新发送数据给显示设备即可。对比的做法是,直接操作显示设备的缓存,这样可能会出现闪屏和额外的绘图开销。这种两个缓存的技术也被成为双缓冲。

脏矩形优化

当界面元素变动时,这个时候我们就要对界面进行重新绘制。
方案一:所有元素重新画一遍
方案二:绘制变动的部分
对比后,明显感到方案二的代价更小,相当于用橡皮擦掉一块,再重新画。

界面有变动时,先将变动的元素所影响的区域标记出来,然后再按顺序绘制和此区域相交的所有元素。

打开Chrome控制台,开启Paint flashing,我们就能看到这样的重绘区域。
浏览器的渲染_第5张图片

为了更加深入的了解这个绘制过程,我用Canvas 2D来演示下,大家可以拷贝这段代码到本地来执行。
打开控制台的Console面板,开启Paint flashing的显示,先后运行s1();s2();s3();s4();并观察重绘区域。在s3中绘制了一整个红色背景,但实际的重绘区域只有很小的一部分,s2的代码是关键。
顺便说一下:(国产的白鹭引擎脏矩形绘制也是这样来实现的)

<!DOCTYPE html>
<html>
<body>
<div>红色的背景上,蓝色的方块,从左侧移动到右侧</div>
<br/>

<div style="width:300px;height:150px;">
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3">
Your browser does not support the HTML5 canvas tag.
</canvas>
</div>


<script> var c=document.getElementById("myCanvas"); var ctx=c.getContext("2d"); ctx.fillStyle="red"; ctx.fillRect(0,0,300,150); ctx.fillStyle="blue"; ctx.fillRect(40,40,40,40); function s1(){ console.log("扣除脏区"); ctx.clearRect(40,40,40,40); ctx.clearRect(150,40,40,40); } function s2(){ console.log("设置重绘区域") ctx.rect(40,40,40,40); ctx.rect(150,40,40,40); ctx.clip(); } function s3(){ console.log("绘制红色背景"); ctx.fillStyle="red"; ctx.fillRect(0,0,300,150); } function s4(){ console.log("绘制蓝色方块"); ctx.fillStyle="blue"; ctx.fillRect(150,40,40,40); } </script>

</body>
</html>

Canvas的绘制和Dom的绘制还是有些区别的,同样的功能用Dom实现如下,这时候再来观察脏区域,是不是比Canvas少了些。Dom实现代码如下:

<!DOCTYPE html>
<html>
<body>
<div>红色的背景上,蓝色的方块,从左侧移动到右侧</div>
<br/>

<div style="position:relative;width:300px;height:150px;background:red">
    <div id="c" style="width:40px;height:40px;left:40px;top:40px;position:absolute;background:blue;"></div>
</div>


<script> function s(){ var c=document.getElementById("c"); c.style.left="150px"; } </script>

</body>
</html>

合成图层加速

接着脏矩形继续讲,如果和脏区域重叠的元素过多怎么办,除了一个个的重新绘制我们还有其他的更好的办法么。
答案是有,想想我们前面讲的,我们目前只有一个绘图上下文(也可以理解成画布、合成的位图)。如果我们有更多的绘图上下文会怎样,当有脏区域时,相交的绘图上下文相当于是缓存,不需要再绘制子元素,此时绘图的开销自然会降低。
在浏览器中这样的图层叫做Graphics layer或者Composited Layer。

浏览器的渲染_第6张图片

利用Chrome的工具,我们也能看到当前网页有多少合成图层。
浏览器的渲染_第7张图片

如何才能使图层成为合成图层呢

  • Layer has 3D or perspective transform CSS properties
  • Layer is used by element using accelerated video -decoding
  • Layer is used by a element with a 3D context or -accelerated 2D context
  • Layer is used for a composited plugin
  • Layer uses a CSS animation for its opacity or uses an animated webkit transform
  • Layer uses accelerated CSS filters
  • Layer has a descendant that is a compositing layer
  • Layer has a sibling with a lower z-index which has a compositing - layer (in other words the layer overlaps a composited layer and should be rendered on top of it)

合成图层也不是越多越好,合成图层是有内存开销的,将不常变动的整体设置成合成图层是比较合理的方案。

硬件渲染

基于GPU,GPU的众核(普通笔记本512核很正常)架构很适合做一些固定的计算,如视频编解码、矩阵转换(裁剪、放大、缩小、倾斜)、位图合并等。

流程

  • 浏览器将每个图层对应的位图上传至GPU,由GPU对各个图层完成合成操作。
  • 对图层的整体操作不需要重新上传位图,只有图层内部变化时才需要重新绘制上传。
  • 位图的生成工作还是由CPU和脏矩形完成。

可以打开链接来感受下Falling Leaves

多线程Tile分组绘图、纹理上传

一个图层对应的位图过大,内部变更后全部重新上传开销太大,为减少传输量,降低内存带宽的消耗,图层采用Tile分组的方式进行管理。

通过Chrome浏览器的控制台我们能很清楚的看到这些过程

浏览器的渲染_第8张图片

你可能感兴趣的:(浏览器,渲染)