腾讯文档Doc Canvas渲染引擎流程改造

为了解决部分历史渲染问题,实现移动端canvas渲染的新功能,以及支持后续功能扩展,对腾讯文档Doc Canvas渲染引擎的流程进行了改造,本文对改造进行介绍和小结。

1. 改造背景

1.1. 解决历史问题

Doc文档滚动过程中偶现渲染空白(safari浏览器出现频率较高):

除了canvas尺寸限制,甚至还有canvas画布占用的显存限制:

所以对于iOS移动端,canvas的使用需要非常谨慎,尽可能减少canvas的数量和尺寸,避免超过限制引发BUG。然而drawImage的使用,依赖额外的离屏canvas,这样相当于直接把canvas的数量乘以了2倍。

  • safari浏览器对drawImage限制,导致渲染白屏

此问题主要集中在safari浏览器,正常滚动文档页面会偶现canvas drawImage不生效导致渲染白屏的问题。

由上述(1)可知,当canvas画布尺寸超过浏览器限制时,会导致canvas绘制失效,safari会在控制台弹出警告:

chrome和safari绘制失败的canvas画布尺寸上限比较一致,但chrome会直接绘制失效,没有任何提示。可以使用试验demo验证: https://xdevilj136.github.io//large_canvas_drawImage_bug.html

Android移动端滚动渲染performance:

由上图对比可以看出,在移动端单次drawImage开销就高达15ms,在单次渲染task中的开销占比非常高,是造成移动端下canvas渲染引擎性能问题的罪魁祸首之一。

2.1.3 canvas分层雪上加霜

渲染层针对不同渲染场景,为了避免无效重绘,提升渲染效能,对不同的渲染内容做了分层。每层渲染拥有独立性,减小重绘粒度,降低了层级间的干扰:

图12 canvas分层导致两个canvas互相覆盖

2.2 编辑场景渲染

2.2.1 编辑场景渲染流程

如图13所示,在编辑文档时,无论编辑的内容范围多大,渲染层都会将整个可视区域+buffer区域(可视区域上下缓冲区域) 作为脏区(需要重新渲染的区域),根据脏区对整个文档的排版DocumentBox进行遍历裁剪并将整个脏区对应的内容进行收集和重新渲染。

图13 编辑场景渲染流程示意图

2.2.2 脏区范围大

对于编辑渲染流程,比较直观的感受便是渲染脏区范围较大,因为在编辑场景渲染层仅仅监听排版变化的layoutChange事件来进行重新渲染,故只能通过可视区域来判断并计算脏区。另外,渲染层仅仅使用两个canvas画布(主内容和overlay)对整个文档进行渲染展示,canvas画布尺寸和脏区大小一一对应,而canvas画布尺寸和canvas渲染耗时是正相关的:

图14 canvas渲染耗时和渲染尺寸相关趋势图 (来源:https://smus.com/canvas-vs-svg-performance/)

所以渲染脏区越大,渲染开销越高,性能越差。主要体验在两方面:

  1. canvas画布尺寸大,渲染耗时高
  2. 渲染的内容多,遍历收集开销更高,特别对于一些嵌套层级可能较深的LayoutBox(如:表格)影响会更大

3. 分页渲染流程改造方案

3.1 滚动场景去掉离屏渲染(drawImage)

通过上述分析,渲染流程上去掉canvas drawImage是比较迫切的需求,而drawImage的调用主要应用在滚动场景的离屏渲染,其作用就是为了尽可能复用渲染内容减少重新渲染。

那么是否有方案可以不使用离屏渲染(drawImage),同时又能复用渲染内容呢?

想到移动端常用的虚拟列表优化方案,可以用来优化长列表滚动性能:

图15 虚拟列表优化方案

虚拟列表通过缓存列表数据,每次仅渲染可视区域对应的item dom节点,上下滚动时可复用dom节点仅更新dom对应的数据或样式,既避免dom数量过多,又减少了销毁和重新创建dom的开销。

Doc文档的滚动实际非常类似,且分页模式下排版结构中分页LogicPage和item可以天然对应起来:

图16 滚动场景分页渲染

分页渲染将每次渲染和复用的最小单位固定为文档的分页(对应排版结构LogicPage),滚动过程中仅仅需要对出现在渲染区域的新分页进行渲染,且新渲染分页可以复用脱离渲染区域的分页DOM,未脱离渲染区域的分页则无需任何更新。

通过这样的流程改造后,有以下收益:

  1. 可以完全弃用离屏canvas和drawImage,解决了drawImage带来的问题,减少了离屏canvas带来的额外显存和总画布尺寸占用
  2. 一个分页对应一个canvas, 减少了单个canvas的尺寸,一定程度上提升了渲染性能

然而以上流程仅仅适用于分页模式,流式模式下整个Doc文档的排版结构只有一个LogicPage(只有一页),为了解决流式模式仍然存在的以上问题且让渲染流程统一,接下来选择对排版层动手:

图17 流式模式排版虚拟分页

如上图所示,对流式模式下的排版进行了调整,将原先整个文档仅有一个分页LogicPage的排版结构,拆分为多个LogicPage,一个LogicPage对应一个虚拟分页。至此,流式模式和分页模式的分页渲染流程完全统一起来。

3.2 编辑场景减少脏区范围

解决完滚动场景下渲染问题,还需要考虑编辑场景。由上述2.2分析可知,原先渲染流程针对编辑场景,是将整个可视区域+buffer视为脏区进行了重新的收集和渲染,渲染脏区范围大。造成这个结果的原因主要是原先渲染层受限于以下两点:

  1. 流式模式下仅一个分页,编辑更新文档无法通过排版层精确获取脏区范围
  2. 分页模式下,虽然能通过排版层精确获取脏区对应的分页范围,但渲染上使用单独的canvas(不考虑分层和离屏)对整屏进行渲染,仍然需要对整个文档剪枝、收集

分页渲染则解决了这些限制,将编辑场景的渲染脏区减少为分页范围:

图18 编辑场景分页渲染

由上图示意,得益于流式模式下的虚拟分页,编辑场景下的脏区范围减少为分页范围,不在脏区的其他分页则可以完全复用,分页模式下也是同理。

注:编辑场景下,也可能出现编辑大范围内容并覆盖了多个分页的情况,这种情况下脏区最大范围也仅仅是可视区域对应的所有分页

3.3 增加canvas回收机制

经过以上改造,分页渲染的基本框架已经确定,但仍然有一些特殊情况需要考虑:

  1. 流式模式下的虚拟分页,排版层暂时还无法处理长图、长表格等内容的拆分,导致存在这些特殊内容排版结果会存在特别长的虚拟分页,进一步导致单个canvas画布特别大且对应渲染范围过大,严重影响渲染性能
  2. 放大页面,可视区域覆盖的分页数量减少,此时为了尽可能dom复用,可以保留不在可视区域的分页视图dom;但会导致放大后的分页对应canvas画布过大(如上述2.1.2的描述,在iOS移动端过大的canvas画布会因为尺寸和显存限制导致canvas渲染失效)

所以,针对以上特殊情况,渲染层增加了canvas回收机制:

  1. 首先对超长的虚拟分页对应的canvas,在渲染层拆分成更细粒度的二级canvas
  2. 对脱离可视区域的canvas, 进行画布回收

canvas回收机制示意图如下:

图19 超长虚拟分页canvas拆分
图20 放大页面回收canvas

其中,对canvas的回收仅仅回收canvas画布,并不对canvas dom进行销毁,避免重新渲染时

增加新建dom开销, 回收逻辑如下:

canvasElement.width = 1;
canvasElement.height = 1;

直接将canvas画布width和height属性置为1,既能清空canvas绘制内容也能回收掉canvas画布占用的显存。

但……为什么不直接将width和height设置为0呢?

可以看下两种回收设置对比:

width = 0, height=0
width = 1, height=1

如上图所示,在safari浏览器,直接将canvas画布设置为width = 0, height=0,虽然画布尺寸确实更新为0,但是占用的显存并没有被浏览器回收。

(注:设置width和height为0进行回收的方式,在chrome可以正常回收显存;且在safari进行测试也是能正常回收,但safari devtools显示内存一直占用,此点尚且存疑)

增加canvas回收机制后,canvas画布所占尺寸和显存前后对比,canvas占用显存和尺寸均下降40%左右,如下图所示:

图21 增加canvas回收机制前后对比

3.4 合并canvas,渲染层级统一管理

由上述2.1.3分析,还存在canvas分层带来的部分问题,main canvas和overlay canvas分层导致canvas画布数量翻倍,且渲染层级的管理无法支持后续扩展功能。

canvas分层目的主要针对切换选区或底色等内容时,可只处理overlay层的渲染,无须重复渲染main canvas (文档主内容),从而提升以上场景时的渲染性能。然而经过分析发现,渲染的开销主要集中在遍历、收集阶段,而非绘制阶段:

图22 单次渲染开销耗时

而canvas分层优化的开销主要是绘制阶段,遍历和收集的开销变化不大;另外,经过分页渲染流程改造后,单次渲染的区域减少进一步降低了绘制的开销。

再者,考虑到要支持环绕浮动元素的层级渲染,将选区、底色等和文档主内容放到同一个canvas层统一进行层级的管理是首选。所以对canvas层级进行合并:

图23 合并canvas示意图

文档主内容和overlay(高亮、底色、选区)全部合并到同一个canvas来进行渲染,不同内容层级可以统一管理,改造后,最终还原多个层级浮动文本框效果如下:

图24 合并canvas后还原浮动文本框效果

4. 总结

经过分页渲染改造,解决了滚动时渲染空白的历史问题,对后续环绕元素的层级渲染提供了支持;最重要的是解决了canvas渲染引擎在移动端的性能问题,使移动端的“分页视图”新功能可以正常使用,让用户可以直接在移动端浏览到和PC端渲染完全一致的Doc文档。

移动端滚动场景优化前后对比:

图25 移动端canvas渲染滚动场景优化前后对比
本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
腾讯文档Doc Canvas渲染引擎流程改造
为了解决部分历史渲染问题,实现移动端canvas渲染的新功能,以及支持后续功能扩展,对腾讯文档Doc Canvas渲染引擎的流程进行了改造,本文对改造进行介绍和...
<<上一篇
下一篇>>