集成终端性能改进
2017年10月3日 Daniel Imms, @Tyriar
集成终端的渲染引擎已针对即将发布的 Visual Studio Code 1.17 版本进行了彻底重写,旨在提升性能。在此版本中,我们从基于 DOM 的渲染系统转向使用 HTML canvas 元素。
DOM 渲染
这有些令人惊讶,但在一个为显示静态文档而设计的系统中,渲染交互式终端是可能的。然而,随着时间的推移,我们发现 DOM 提供的某些功能需要被覆盖才能解决几个问题
选择:在覆盖终端使用场景时,与 DOM 的选择系统进行了大量对抗。由于我们总是只渲染 DOM 可见的内容,因此如果不重新实现选择功能,就无法选择多页内容。滚动还会导致选择被取消。为了解决这些问题,我们添加了自定义选择逻辑。
字符未对齐:由于许多等宽字体对某些 Unicode 字符并非严格等宽,这可能导致像下面图片右侧所示的情况
一个解决方法是将所有 Unicode 字符包裹在固定宽度的 span 中,但这会增加渲染一帧所需的时间。
过度的垃圾回收:由于渲染终端所需的元素数量,垃圾回收器需要频繁清理内存,这通常会显著延长渲染时间。为了尝试解决此问题,我们设置了一个对象池,允许 DOM 元素被回收利用。
性能:无论我们如何努力解决这些问题,性能总是会受到布局引擎施加的硬性限制。
绕过布局引擎
在某些情况下,仅合成元素和进行布局就可能比一帧(16.6ms)花费更长时间,这对于我们希望在终端中保持流畅的每秒 60 帧(FPS)是不可接受的。解决方案是采用一种新的基于 canvas 的渲染引擎。
<canvas>
HTML 元素允许使用 JavaScript API 绘制图形和文本。
渲染层
多个被称为“渲染层”的 canvas 元素用于简化终端不同部分的渲染。
当前的层,按顺序,是
- 文本:背景色和前景色文本,此层是不透明的。
- 选择:使用鼠标进行选择。
- 链接:悬停在链接上时显示的下划线。
- 光标:终端的光标。
将这些部分分离成各自的小组件,极大地简化了绘制过程。
仅绘制已更改的内容
新渲染器的一个重要部分是它只绘制已更改的内容。为此,它维护一个精简的内部模型,其中包含单元格绘制状态的最小信息量。然后,该状态用于在执行更昂贵的绘制操作之前,快速检查单元格何时需要更改。对于文本层,此模型包括对字符、文本样式、前景色和背景色的引用。
这与之前的渲染引擎形成对比,后者即使在没有内容更改的情况下,也会将整行从 DOM 中移除、重建并重新添加。
上图中绿色的矩形表示被重绘的区域。
纹理图集
纹理图集用于进一步提升渲染时间。在后台有一个 ImageBitmap
,它包含了默认背景色上最常见样式的所有 ASCII 字符。
在绘制这些样式的文本时,使用纹理图集而不是常规调用 CanvasRenderingContext2D.fillText
。由于 ImageBitmap
与 GPU 共同定位,绘制速度得到了显著提升。
强制跳帧
由于 DOM 渲染的速度,有必要跳过额外的帧以确保解析器有足够的 CPU 时间。虽然这使得命令运行速度比以前更快,但大量数据流经终端会导致帧率降至 10 FPS 以下。
有了新的渲染器,此限制已被解除,您现在可以在终端中享受高达 60 FPS 的帧率。
结果
我们的基准测试表明,集成终端现在根据情况,渲染速度大约是以前的 5 到 45 倍。即使您没有注意到响应速度和帧率的提升,更快的渲染也意味着更少的电池使用!我们希望您喜欢这些性能改进,它们将在几天内随 VS Code 1.17 版本推出,并且现在已可在 Insiders 版本中测试。
您还可以查看添加此功能的 原始 xterm.js 拉取请求,以了解更多详细信息。
编码愉快!
Daniel Imms,VS Code 团队成员 @Tyriar