《通过减少 draw call 提升渲染性能-沧东》演讲视频 + 文字版

简介: 《通过减少 draw call 提升渲染性能-沧东》演讲视频 + 文字版
编者按:本文是蚂蚁集团 AntV 工程师沧东在 SEE Conf 2022 的演讲内容,包括演讲视频及文字内容,欢迎享用。


大家好,我是来自 AntV 团队的沧东。今天给大家带来一个渲染相关的话题,如何通过减少 draw call 提升渲染性能。今天的分享会分成以下几个部分:介绍优化的场景,draw call 代表的含义,它的数量增长对于渲染性能的影响,结合 Canvas2D 和 WebGL API 减少 draw call 以及其他性能提升手段。

首先我们简单介绍一下我们的优化场景,相信听完第一场演讲的同学应该对图场景有了一个大概的了解。在一个典型的图场景里,我们需要渲染大量的 2D 图形例如,节点、边以及文本。这些 2D 图形相对比较简单,也不需要用到追求真实感的材质,它的显著特点就是数量非常多。我们希望在成千上万的数量级别下,用户在做类似于平移缩放、高亮这样的交互分析过程中,依然能够保持平滑的流畅体验。这里我们展示了优化之前后的效果对比,用户在进行缩放或者拖拽的时候,能看到它的交互是非常卡顿的,帧率在 60 FPS 以下。我们希望在应用一系列的优化手段之后能够达到流畅交互。紧接着我们会引出今天的主题 draw call,我想简单带大家做粗浅的认识一下 CPU、GPU 和浏览器是如何协同工作的。当我们使用浏览器浏览网页时,浏览器本身会去调用系统级别的渲染API,可能是 OpenGL 或者 Vulkan 等等,它依据不同的操作系统而异。这些渲染 API 会去访问机器上底层的显卡,最后把我们的网页渲染到屏幕上,这是浏览器渲染网页的基本流程。对于我们前端开发者来说,在使用 Canvas2D 或者 WebGL 这样的渲染 API 时,同样也会发生从 CPU 到 GPU 的交互。当我们使用 Canvas2D API 绘制 2D 图形或者文本时,浏览器会去调用底层的 2D 渲染库,例如 Skia,由它向 GPU 发送绘制命令。我们把这次从 CPU 到 GPU 的交互或者调用称作为一次 draw call。在我们开头的那个很复杂的图场景下,draw call 数量会很多,因为有成千上万的点和边。那 draw call 的数量这么多,对于我们的渲染性能究竟有哪些影响呢?为了更好地理解这一点,我们展示了一张 CPU 和GPU 的渲染时间线。首先大家可以看到,在这样一条时间线里,CPU 在每一帧需要做同一件事,就是准备 draw call。它得告诉 GPU 要画哪些东西,需要准备的 draw call 数量越少,相应的准备时间也就越短。所以在第一帧里大家能看到这个时间线非常短。但随着要准备的 draw call 数量越来越多,这个准备时间就会被拉长了。GPU在渲染之前,首先需要先拿到 CPU 在上一帧准备好的 draw call。在第二帧中,由于第一帧 draw call 数量少,因此 GPU 很快就能进行渲染,它的渲染时间每一帧会在 16 毫秒之内,也就能达到所谓 60 FPS 的流畅渲染效果。但此时它并不能马上开始下一帧的渲染,因为它需要等待 CPU 完成这个第二帧的准备工作,因此 GPU 会有一段空闲时间。由于有这个空闲时间的存在,可以直观的看到在第二帧中渲染时间就被拉长了。这给用户的直观的感受就是帧率从 60 FPS 下降到 45 FPS。因此在了解清楚了这一切之后,我们就能得到一个核心观点,就是 CPU 准备 draw call 的时间是比较长的。GPU 相对来说渲染是比较快的,因此它有大量的空闲时间。所以我们接下去的优化就会围绕怎么去把这个 CPU 准备 draw call 的时间给降下来,让它准备尽量少的 draw call,这样我们的渲染性能就能得到提升。我们接下去会围绕 Canvas 2D 以及 WebGL 这两种 API 给出三种减少 draw call 的优化方法:剔除、脏矩形渲染以及合批渲染。我们在衡量优化性能的效果的时提供了两个指标。第一个当然是这个 draw call 次数,第二个 CPU 求交次数,后面会解释这个指标它代表的含义。总之我们希望这两个指标的数量尽可能少。首先会给大家介绍第一种优化手段:剔除。当用户在一个大图中进行缩放操作时,能看到的区域其实只是图的局部,能见范围之外的图形就可以不用渲染。大家可以看右边这张图,在实际上用户能看到的这个区域内可能只有三个图形,除此之外的 45678 号图形都不用渲染。那我们怎么判断一个图形是否要渲染呢?这个标准我们会通过包围盒与视口求交的方式来判断。大家可能会有疑惑,就为什么你这儿的图形都是一个个块儿,而我们实际看到的那些 2D 图形可能有圆、椭圆、直线甚至文本。这是因为我们为了把求交的过程变得简单和快,会用一个包围盒去框住每一个 2D 图形。不管 2D 图形再怎么复杂,总归可以用一个矩形去把它框住。在右侧这个简单场景里,通过求交能够把 draw call 数量从八个变成三个。但随之而来的是我们为了减少 draw call,又给CPU 加了一个活儿,它需要去依次遍历场景中的每个图形。因此会增加八次求交运算。回到我们回到我们开头的那个场景里,虽然 draw call 次数降低了,但是随之而来的求交次数上升了。因此我们需要对剔除这个场景进行一些优化过程。第一个优化手段是借助场景图的信息。用户在使用渲染引擎 API 时,通常都会使用类似于场景图这样的方法去描述场景。用户提供的层次化的信息,能够有效的帮助我们进行求交加速。同样还是以右侧这个简单场景为例,一旦我们知道了4567 之间存在父子关系,就可以跳过对于 567 的求交检测,因为我们发现4号已经在视口之外了。除了利用场景图信息帮助我们加速这个过程,还可以利用空间索引。对于判断或者检测视口里面到底有哪些图形这个问题,我们其实可以换一种想法,把它转换成区域查询这样的思路。它有点类似于我们在数据库里去查询数据,我们希望通过一次的查询,一次性拿到我们当前的视口里面到底有哪些图形。我们这里使用 R 树这样的空间索引,它能够为靠的比较近的图形自动创建索引,或者把它合并成一个节点。在右侧这个例子里面大家能看到,因为 45678 这几个图形靠的比较近。所以当我们在建立索引的时候,会自动的把它合成为一个节点。这样在进行包围盒的检测过程中,整个 45678 只要通过一次的求交检测,就完全能够排除在视口之外了。求交次数会从5次下降到4次。在我们开头那个非常大的场景中,能够显著减少 CPU 的求交次数。讲完了剔除,下一个优化手段叫脏矩形渲染。当我们在图表里做类似高亮操作时,被高亮图形之外的其他区域其实都是不变的。对于渲染引擎来说,不希望去重复的渲染这些不变的区域,只需要渲染这些发生增量更新的区域。它有点像 React 这样视图框架中的 diff 算法,需要去精准的找到真正发生的部分,然后以最小粒度重绘它。在右侧这个场景里,当鼠标悬停到这个1号图形时,第一步需要去找到这个所谓的“脏矩形”,同样会利用到刚刚介绍的包围盒以及区域查询的概念。这个虚线区域可以理解为是一个需要重绘和更新的区域。找到这个区域之后,下一步我们还是需要去通过一次区域查询去找到这个脏矩形里面到底包含了哪些图形。也就是我们这次需要去重绘的部分。这里我们很快找到是1号2号这两个图形。紧接着我们只需要清空这个局部区域,而非整个画布。最后重绘1号2号图形就完成了这次更新。在这样的过程中,我们会发现 draw call 数量从四次下降到两次。接下来要介绍的优化手段,如果大家对 WebGL API 不熟悉也没有关系。我们简单介绍它的核心思想。图场景里有大量重复的图形,比如所有的节点可能都是圆,所有的边都是直线。这些大量重复的图形可以合并成一个图形,合批渲染就使用了这样的核心思路。在使用 Canvas 2D 这样的 API 绘制时,当我们想画 8000个圆,就不得不重复的调用 8000 次 API。而且它是串行的,发生在 CPU 端,一旦我们使用像 WebGL 这样更加底层的 API 时,就有机会去做一个整合。我们可以把这些不同的圆中不同的属性,比如它的颜色、位置或者半径,都把它整合成一起。然后一次性的提交给底层的渲染 API。因此利用 WebGL 只需要用一次 draw call 就能完成这样大量同类图形绘制。利用它我们可以最大程度的把 draw call 次数从千或者万级别下降到个位数。因此它的性能提升效果也是最明显的。以上介绍的三种手段都是围绕减少 draw call。当然除了它之外,大家看到的各种渲染引擎中还有其他的性能提升手段。比如像 Oasis 这样的注重于真实感或模型渲染的渲染引擎,会尽可能去压缩模型或者贴图。再比如像 G6 这样的上层图应用会采用 LOD 手段,例如当用户的缩放等级比较高时,可以人为的把一些小的文本隐藏掉。我们接下来介绍的一种优化手段,希望让 GPU 承担更多的计算任务,从而减轻 CPU 的计算压力。回到刚刚的时间线里,大家可以看到 GPU 无论是渲染还是计算都是非常快速的。因此在等待 CPU 准备 draw call 或者完成其他任务时,可以尽量利用好这部分空闲时间。这里有一个常见的优化点,当我们在对整个图或者画布进行拖拽,即画布级别的操作时。比如大家看右侧从左边拖到右边,整个场景其实都发生了变化。因此这个场景中每个图形必然也要发生重绘。在这样的场景里面每个图形的位置显然发生了变化。如果我们把位置的计算放在 CPU 端进行,这仍然是一个串行的过程,我们会写一个 8000 次左右的循环,依次去计算每一个图形在世界坐标系下的变换矩阵。大家仔细想想,对于每一个图形变换矩阵的计算,实际上是可以通过并行完成的。我们把它交给 GPU 写在 shader 中计算。虽然计算次数上没有变化,但它从串行变成并行了,也能够很好的利用 GPU 的空闲时间。除此之外,GPU 当然还可以做很多和渲染无关的操作,我们把它叫做 GPGPU 通用计算。在很多现代化的渲染引擎中会提出类似 GPU 驱动的渲染这样的概念。我们之前介绍的剔除实际上也是可并行的,现在我们把剔除放在 CPU 端做,未来借助像 WebGPU 这样的更底层、能力更强的 API,我们可以把剔除工作交给 GPU 做。以上我们介绍的那些手段已经整合在 AntV 底层渲染引擎中。扫码可以了解我们更多优化相关的话题。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
13天前
|
运维 物联网 Serverless
函数计算产品使用问题之怎么提高并发绘图
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
22天前
|
JavaScript 前端开发 算法
【Vue秘籍揭秘】:掌握这一个技巧,让你的列表渲染速度飙升!——深度解析`key`属性如何成为性能优化的秘密武器
【8月更文挑战第20天】Vue.js是一款流行前端框架,通过简洁API和高效虚拟DOM更新机制简化响应式Web界面开发。其中,`key`属性在列表渲染中至关重要。本文从`key`基本概念出发,解析其实现原理及最佳实践。使用`key`帮助Vue更准确地识别列表变动,优化DOM更新过程,确保组件状态正确维护,提升应用性能。通过示例展示有无`key`的区别,强调合理使用`key`的重要性。
39 3
|
11天前
|
图形学 开发者 搜索推荐
Unity Asset Store资源大解密:自制与现成素材的优劣对比分析,教你如何巧用海量资产加速游戏开发进度
【8月更文挑战第31天】游戏开发充满挑战,尤其对独立开发者或小团队而言。Unity Asset Store 提供了丰富的资源库,涵盖美术、模板、音频和脚本等,能显著加快开发进度。自制资源虽具个性化,但耗时长且需专业技能;而 Asset Store 的资源经官方审核,质量可靠,可大幅缩短开发周期,使开发者更专注于核心玩法。然而,使用第三方资源需注意版权问题,且可能需调整以适应特定需求。总体而言,合理利用 Asset Store 能显著提升开发效率和项目质量。
23 0
|
2月前
|
vr&ar 图形学 UED
优化图形渲染与物理模拟:减少Draw Calls与利用LOD技术提升性能
【7月更文第10天】在现代游戏开发和实时渲染应用中,性能优化是至关重要的环节,它直接关系到用户体验的流畅度和真实感。本文将深入探讨两种关键技术手段——减少Draw Calls和使用Level of Detail (LOD) 技术,来提升图形渲染与物理模拟的效率。
63 2
|
4月前
|
前端开发 UED 开发者
【专栏】探讨了CSS3动画卡顿的原因,包括复杂动画效果、过多元素参与、低效代码结构和硬件资源限制,并提出优化措施
【4月更文挑战第29天】本文探讨了CSS3动画卡顿的原因,包括复杂动画效果、过多元素参与、低效代码结构和硬件资源限制,并提出优化措施:简化动画路径、控制元素数量、优化代码结构、利用硬件加速及性能监测。通过实际案例展示了优化效果,强调了性能优化对提升用户体验的重要性。在开发中,应持续关注并优化动画性能,以适应网页应用的需求。
254 1
|
4月前
|
程序员 API
Compose:警惕Loop(遍历),图文并茂带你深度释疑,解决的不仅是性能问题
Compose:警惕Loop(遍历),图文并茂带你深度释疑,解决的不仅是性能问题
101 0
|
定位技术 API 图形学
unity-2D游戏官方案例--带视频案例(1)(层级渲染,物理碰撞,粒子动画,UI等多位基础一体化)
unity-2D游戏官方案例--带视频案例(1)(层级渲染,物理碰撞,粒子动画,UI等多位基础一体化)
167 1
|
数据采集 缓存 数据可视化
Echarts高级进阶教程:图表渲染大数据量导致卡顿加载时间慢等问题的解决方案
Echarts高级进阶教程:图表渲染大数据量导致卡顿加载时间慢等问题的解决方案
1692 0
Threejs入门进阶实战案例(4):addEventListener() 方法自适应窗口显示的方案
Threejs入门进阶实战案例(4):addEventListener() 方法自适应窗口显示的方案
95 0
|
前端开发 JavaScript UED
前端工程化的前端性能的性能指标之首次绘制(FP)
首次绘制(First Paint)是前端性能的一个重要指标,因为它是用户体验的一部分,并且对于网页的响应速度和可接受性有很大的影响。
130 0