2

复杂渲染引擎架构与设计--4.渲染计算

 1 year ago
source link: https://godbasin.github.io/2023/08/17/render-engine-calculate/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面《收集与渲染》一文中,我们简单提到说在一些复杂场景下,从服务端获取的数据还需要进行计算,比如依赖 Web 浏览器的计算,亦或是游戏引擎中的碰撞检测。

本文我们详细针对复杂计算的场景来考虑渲染引擎的优化。

渲染引擎完整的数据流向

对于需要进行较复杂计算的渲染场景,结合收集和渲染的架构设计,我们完整的渲染流程大概应该是这样的:

render-engine-calculate-1.jpg

可见,完整的渲染流程里,计算的复杂程度会直接影响渲染是否及时,最终影响到用户的交互体验。在这里,我们还是以在线表格为例子,详细介绍下为什么有如此大的计算任务。

渲染引擎为什么需要计算

在表格中,画布绘制所需的数据,并不能完全从数据层中获取得到。对于以下一些情况,需要经过渲染引擎的计算处理才能正确绘制到画布上,包括:

1. 分行/换行计算(计算范围:格子)

如下图,当单元格设置了自动换行,当格子内容超过一行会被自动换到下一行。由于内容宽度的测量依赖浏览器环境,因此也是需要在渲染引擎进行计算的:

render-splitter-calculate-2.jpg

2. 行高计算(计算范围:整行)

当某个行没有设置固定的行高时,该行内容的高度可能会存在被自动换行的单元格撑高的情况,因此真实渲染的行高也需要根据分行/换行结果进行计算。

3. 覆盖格/隐藏格计算(计算范围:整行)

如下图,在没有设置自动换行的情况下,当单元格内容超出当前格子,会根据对齐的方向、该方向上的格子是否有内容,向对应的方向拓展内容,呈现向左右两边覆盖的情况:

render-splitter-calculate-1.jpg

4. 边框线计算(计算范围:整行)

受覆盖格影响,覆盖格和隐藏格(即被覆盖的格子)间的边框线会被超出的内容遮挡,因此对应的边框线也会受影响。

以调整列宽为例子,该操作涉及的计算包括:

render-splitter-calculate-3.jpg

可见,除了分行计算只涉及该列格子,一次列宽操作几乎涉及全表内容的计算,在大表下可能会导致几秒的卡顿,在一些低性能的机器上甚至会达到十几秒。由于该过程为同步计算,网页会表现为无响应,甚至严重的情况下会弹窗提示。

计算过程优化

之前我在《前端性能优化–卡顿篇》一文中,有详细介绍对于大任务计算的优化方向,包括:

  1. 赋值和取值的优化。
  2. 优化计算性能和内存,包括使用享元的方式来优化数据存储,减少内存占用
    及时地清理不用的资源,避免内存泄露等问题。
  3. 计算大任务进行拆解。
  4. 引入其他技术来加快计算过程,比如 Web Worker、WebAssembly、AOT 技术等。

对于较大型的前端应用,即使并非使用 Canvas 自行排版,依然可能会面临计算耗时过大的计算任务。当然,更合理的方式是将这些计算放在后台进行,直接将计算完的结果给到前端使用。

也有一些场景,尤其是前端与用户交互很重的情况下,比如游戏和重编辑的产品。这类产品无法将计算任务放置在后端,甚至无法将计算任务拆分到 Web Worker 进行计算,因为请求的等待耗时、Worker 的通信耗时都会影响用户的体验。

对该类产品,最简单又实用的方法便是:拆。

将计算任务做拆分

将计算任务做拆分,我们可以结合计算场景做分析,比如:

  • 只加载和计算最少的资源,比如首屏的数据
  • 只进行可视范围内的计算和渲染更新,在可视区域外的则做异步计算或是暂不计算
  • 支持增量计算和渲染,即仅变更的局部内容的重新计算和渲染
  • 支持降级计算,对计算任务做优先级拆分,在用户机器性能差的情况下考虑降级渲染,根据优先顺序先后计算和绘制
  • 设计任务调度器,对计算任务做拆分,并设计优先级进行调度

比如,React16 中新增了调度器(Scheduler),调度器能够把可中断的任务切片处理,能够调整优先级,重置并复用任务。

调度器会根据任务的优先级去分配各自的过期时间,在过期时间之前按照优先级执行任务,可以在不影响用户体验的情况下去进行计算和更新。

通过这样的方式,React 可在浏览器空闲的时候进行调度并执行任务。

预计算/异步计算

还有一种同样常见的方式,便是将计算任务进行拆分后,通过预判用户行为,提前执行将用到的计算任务。

举个例子,当前屏幕内的数据都已计算和渲染完毕,页面加载处于空闲时,可以提前将下一屏幕的资源获取,并进行计算。

这种预计算和渲染的方式,有些场景下也会称之为离屏渲染。离屏渲染同样可以作用于 Canvas 绘制过程,比如使用两个 Canvas 进行交替绘制,或是使用 worker 以及浏览器提供的 OffscreenCanvas API,提前将要渲染的内容计算并渲染好,等用户进入下一屏的时候可以直接拿来使用。

如果是页面滚动的场景,还可以考虑复用滚动过程中重复的部分内容,来节省待计算和渲染的任务数量。

这些方案,我们后面都会详细进行一一讨论,本文就不过多描述了。

或许很多开发同学都会觉得,以前没有接触过大型的前端项目,或是重交互重计算的产品,如果遇到了自己不知道该怎么做优化。

实际上,大多数的优化思路都是相似的,但是我们需要尝试跨越模板,将其应用在不同的场景下,你就会发现能得到许多想象以外的优化效果。

纸上得来终觉浅,绝知此事要躬行。不要自己把自己局限住了哟~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK