Game Optimize:降低纹理切换优化drawcall调用

| 分类 Performance  | 标签 optimize 

一直以来,游戏都是推动硬件更新的主要手段。其原因无非是游戏对真实性的追求永无止境,而硬件的发展始终得遵循客观物理世界。这种矛盾下的结果就是,游戏对性能二字异乎敏感。可以说,每一个成熟的游戏开发者最终都会与之打交道。

终日忙于游戏功能的开发,在项目接近尾声时,终究还是产生了性能问题。表现出来的自然是运行卡顿,帧数低,发热严重。简单优化一下,还是有成效的。

优化游戏的快感不亚于做出游戏,甚至更甚之


祸源

要进行优化,首先得知道热点在哪。一般来讲,查找性能瓶颈还是要使用工具的好。

有些人对使用工具似乎有天生的排斥感,感觉使用了工具,自己就掉价了。问题在于,人与动物的区别不就在于制造工具并使用工具吗?有些说我不需要工具,掐指一算,两眼一转就知道是哪出了问题。可惜,你要是这么厉害,写出来的代码就不该有问题了。

言归正传,出现性能问题后,无非两方面,CPU端和GPU端(有点废话)。与CPU端相关的一般是代码逻辑复杂内存访问过频,与GPU端相关的一般是顶点过多shader逻辑复杂纹理数据等等。

手中的游戏是iOS游戏,那instruments自然是不二之选。使用instruments 运行游戏,随着游戏的进行,观察到GPU端的负荷居高不下。然后在最为卡顿的场景里,做GPU Capture,等待一会,可以在Xcode的左栏看到当前场景一帧的所有绘制调用。

然后逐个查阅每个绘制调用,最后发现,果然是很常见的问题,纹理切换过于频繁导致drawcall过高


渊源

drawcall这个东西与游戏和游戏引擎无关,它实际是底层渲染框架OpenGL中的一个概念(Direct 3D中也有类似的概念)。我们的游戏运行于iOS上,其中所有的绘制最终都要通过OpenGL去驱动GPU完成。每一次drawcall都要求OpenGL走一遍渲染流水线,驱动GPU绘制一次。显而易见,最极端的绘制效率是一次绘制完成,对应的就是只调用一次drawcall。

当前的OpenGL是使用的是状态机模型,意思是,整个OpenGL由无数状态组成,如果这个总状态一直不变,那就可以达到所谓的一次绘制。而事实上,不同的顶点切换,纹理切换都会造成状态的改变,毕竟游戏就是要有变化啊。

回到我们的游戏上来,查看绘制栈发现,我们的游戏在纹理组织上有着重大的缺陷(当然,这和游戏本身的玩法也有点关系)。具体的表现就在于,单次绘制太少,频繁的切换要绘制的纹理,使得OpenGL不断的在进行bindtexture,gentexture,drawelement操作,而每次drawelement竟然只作用了一张很小的纹理。更严重的是,在之后又使用了同样的纹理。

我们这里的纹理可以简单的代指单张图片。

到这里,我们可以明白,因为OpenGL的绘制原理,我们不可以频繁切换OpenGL的状态,最好是让OpenGL满负荷进行绘制,即,尽可能的一次drawcall中塞满绘制内容


解决之道

知晓原因之后就是查到产生造成如此多纹理切换的具体代码所在了。然后具体的优化,不外乎两点:

  • 在代码层面将相同层的绘制代码尽量放在一起,做到缓存友好,不然重新从内存经CPU读取再复制到GPU就太慢了
  • 在资源管理层面将联系紧密的图片打包成一张大图,对具体小图块的绘制在OpenGL看来都是在同一张纹理上,就不会有纹理切换。

两者结合下来,对于简单的场景,特别是2d游戏,可以将drawcall降至个位数。


总结

  • 性能分析,代码分析请优先使用工具
  • OpenGL是状态机模型,尽可能少的造成状态切换
  • 对于纹理占比重的游戏,合理安排纹理绘制顺序,合理组织图片资源,做到缓存友好,最大化绘制

Previous     Next