为什么要减少 DrawCall?
其实我们真正需要减少的并不是 DrawCall 这个行为本身,而是减少每个 DrawCall 前置的一些消耗性能和时间的行为。
举个栗子
问:尝试在两个硬盘之间传输文件,传输 1 个 1MB 的文件和传输 1024 个 1KB 的文件,同样是传输了共 1MB 的文件,哪个更快?
答:传输 1 个 1MB 的文件要比传输 1024 个 1KB 的文件要快得多得多。因为在每一个文件传输前,CPU 都需要做许多额外的工作来保证文件能够正确地被传输,而这些额外工作造成了大量额外的性能和时间开销,导致传输速度下降。
回到渲染
图形渲染管线的大致流程如下:
上图只是对渲染管线的部分概括,方便大家理解,实际的图形渲染管线比较复杂,不在本文讨论范围内。
从图中可以看到在渲染管线中,在每一次 DrawCall 前,CPU 都需要做一系列准备工作,才能让 GPU 正确渲染出图像。
而 CPU 的每一次内存显存读写、数据处理和渲染状态切换都会带来一定的性能和时间消耗。
到底是谁的锅?
一般来说 GPU 渲染图像的速度其实是非常快的,绘制 100 个三角形和绘制 1000 个三角形所消耗的时间没差多少。
但是 CPU 的内存显存读写、数据处理和渲染状态切换相对于 GPU 渲染来说是 非常非常慢 的。
实际的瓶颈在于 CPU 这边,大量的 DrawCall 会让 CPU 忙到焦头烂额晕头转向不可开交,而 GPU 大部分时间都在摸鱼,是导致游戏性能下降的主要原因。
关于动态合图:
Cocos Creator 提供了在项目构建时的静态合图方法—— 自动合图(Auto Atlas)。但是当项目日益壮大的时候贴图会变得非常多,很难将贴图打包到一张大贴图中,这时静态合图就比较难以满足降低 DrawCall 的需求。所以 Cocos Creator 在 v2.0 中加入了 动态合图(Dynamic Atlas)的功能,它能在项目运行时动态的将贴图合并到一张大贴图中。当渲染一张贴图的时候,动态合图系统会自动检测这张贴图是否已经被合并到了图集(图片集合)中,如果没有,并且此贴图又符合动态合图的条件,就会将此贴图合并到图集中。
动态合图是按照 渲染顺序 来选取要将哪些贴图合并到一张大图中的,这样就能确保相邻的 DrawCall 能合并为一个 DrawCall(又称“合批”)。
启用动态合图会占用额外的内存,不同平台占用的内存大小不一样。目前在小游戏和原生平台上默认会禁用动态合图,但如果你的项目内存空间仍有富余的话建议开启。
若希望强制开启动态合图,请在代码中加入:
cc.macro.CLEANUP_IMAGE_CACHE = false;
cc.dynamicAtlasManager.enabled = true;
贴图限制:
动态合图系统限制了能够进行合图的贴图大小,默认只有贴图宽高都小于 512 的贴图才可以进入到动态合图系统。用户可以根据需求修改这个限制:
cc.dynamicAtlasManager.maxFrameSize = 512;