FPS优化

来源:互联网 发布:淘宝云购在哪 编辑:程序博客网 时间:2024/04/27 10:25

FPS即Frame Per Second,是游戏对用户体验非常重要的一项技术指标。

目录

  • 1 CPU/GPU
  • 2 针对CPU进行优化
    • 2.1 瓶颈
    • 2.2 常见的优化
    • 2.3 Loading效率优化
  • 3 GPU优化
    • 3.1 辨别顶点着色器和像素着色器瓶颈
    • 3.2 常见顶点着色器性能问题
    • 3.3 常见像素着色器性能问题

CPU/GPU

游戏软件的结构一般都有固定的主循环或主Update。正确的循环需要将Update和Render分开,以减小空闲等待时间,最大化利用CPU和GPU。

  • 图形API调用的过程可以看作是向驱动程序发送指令的过程,这些指令存放在驱动内部的队列中,在后台执行。大部分情况下,CPU中执行的代码不需要等待GPU完成实际的渲染过程,除非需要读取GPU中的结果时或需要开始下一帧时。
  • 当一帧渲染请求发送完毕后,程序可以进行下一帧的Update,而驱动程序在后台完成实际的渲染过程。在这种协作模式下,CPU中进行的Update和GPU中的顶点转换、像素生成同时进行。
  • 当要开始下一帧的Render时,必须完成上一帧的渲染,这样才能生成完整的画面。OpenGLES可以通过glFinish或glClear完成这种同步。由于glClear会等待排队中的翻屏操作(eglSwapBuffers 或presentFrameBuffers)完成,所以glFinish函数实际很少使用。

在xocde5中启动程序可以在,很容易地看到CPU和GPU占用率,这样很容易判断哪些部分需要优化。

但如果没有xcode,要判断FPS,可以跳过某些帧的渲染来判断是否因渲染引起了FPS降低,例如每隔一次Update才渲染一次。

OpenGLES Analyze.jpg


针对CPU进行优化

针对CPU进行优化就是要找到性能瓶颈,然后逐个解决它们。

CPU优化最容易进入的误区就是,在找到瓶颈前,就武断地对哪些看上去可能最影响帧数的代码进行修改。

瓶颈

所谓的性能瓶颈就是好用CPU时间最多的代码。检测代码的耗时,基本的原理是在关键位置分别计时,求时间差。 如果在代码里添加很多取时间和求实检差的代码,很容易导致代码量迅速增加,导致代码可读性变差。可以用以下方法减少代码量:

  1. 利用对象在作用域内构造和析购的特性,分别计算开始和结束时间,这样只要在被测试代码处添加一个临时对象;
  2. 利用一个可复用的函数,在每个分段点计时,并计算上一段的耗时。
  3. 利用C++内置宏,__FILE__, __LINE__,__FUNCTION__自动获取函数名字。
  4. 利用对象作用域计时的方法,还可以获取两级调用间的关系,在统计结果中分层显示。
  5. 非常重要,利用glfDebugger,以上方法在glf中都已经实现,不少游戏实现了按F1启动glfDebugger,它能图形化显示,需要在被测试代码中添加一些宏标记。需要注意的是,glfDebugger利用局域网通信,如果测试iPhone设备,需要让设备和PC在同一个局域网内。

常见的优化

下边的常见问题,是代码编写中应该注意的;也可以在优化代码时作为参考。

  • 在Loading界面以外,避免读写文件;
  • 在Loading界面以外,避免内存分配;

特别是有些临时对象,如果经常使用,应该定义成员变量,保持和所属对象相同的生命周期;

尽量利用栈上的空间,例如一个小于1k字节的临时对象,就不应该用new创建在堆上了,直接在栈上更方便,不用考虑析购问题。

  • 优化形成瓶颈的算法

算法是否成为瓶颈应该已实测的CPU时间为依据。 由于FPS优化一般是在项目后期进行,所以要尽量最小化修改,避免不必要的修改,所以要以能影响大小作为优化的优先级。

  • 不良的线程锁,导致等待时间过长这种情况下,应该检查是否可以缩短对象的锁定时间,只在需要占用对象的地方加锁。

Loading效率优化

所谓的Loading优化,就是提高Loading的效率,缩短进入游戏的时间。上边的方法经常能帮助提高FPS。但更多时候需要配合一些资源的优化进行。 优化的方式并不是固定的,仍然要根据实际情况来做优化。 下边的一些情况,是平时开发中应该注意的问题。

  • 尽量降低资源大小,优化资源大小对于降低安装包也有很大帮助。
  • 避免解析过多复杂的脚本,最总发布版尽量用二进制。
  • 对容器的使用进行优化,例如在已知std::vector数组大小的情况下,添加数据前可用reserve预先分配足够的空间,可以减少内存重新分配和对象拷贝的时间;同时提高内存使用率。
  • 二进制数据尽可能地memory baking,也就是将一个数组直接读入到内存,而不是逐个数据进行读取。
  • 大量字符串的集合经常不需要转换成std::string,可以建立索引和map,同时提高loading效率和内存使用率。
  • 尝试将load任务放在队列中,使用可控数量的线程进行加载;
  • streaming(动态加载),例如纹理在一开始只加载低级lod,等进入近距离时才加载完整部分;适合streaming的还有动画、较大的游戏数据,关卡。

GPU优化

现在的GPU,无论是固定管线或可编程管线,都分成顶点着色器和像素着色器。它们以流水线的方式协作,自然地,顶点着色器和像素着色器都可能成为性能瓶颈。 GPU优化的方法通常有这样几种:

  • 排除法,将一些物件隐藏(不渲染它们),观察FPS变化;将一类物件隐藏起来,例如半透明/alpha test物件/带蒙皮动画的物件;
  • 比较法,对影响帧数的物件,横向比较它们和其他物件的差别,并尝试修改后作纵向比较;
  • 套用规律,对于特定的设备,可以利用一些规律进行优化,例如iPhone4这样的tiler设备处理alpha test很慢,如果场景中有很多alpha test,就需要减少alpha test的像素的面积;

但规律总是变化的,不能对所有设备一概而论。

辨别顶点着色器和像素着色器瓶颈

顶点着色器用于对顶点进行三维空间转换,最终生成顶点在屏幕上的位置、纹理坐标、光照参数等信息供像素着色器使用。顶点着色器接收应用程序输入的顶点信息,利用预先设置好的矩阵,光源,雾化等信息,或Shader程序和参数,完成顶点转换。

像素着色器的作用是利用顶点着色器的输出光栅化后生成的像素的基本信息,逐个生成像素输出到缓冲区中。像素数量取决于屏幕的分辨率,单个像素计算的复杂度取决于固定管线的参数和像素着色器程序。

由于像素着色器总的计算复杂度可由分辨率控制。因此,可以通过降低分辨率是否影响帧数来判断,瓶颈是否在像素着色器。

常见顶点着色器性能问题

这些问题通常都和硬件有关,不能单独脱离硬件环境评估渲染是否需要优化。 常见的硬件瓶颈

  • 过多的顶点,这是最常影响像素着色器耗时的指标,但并不意味着遇到像素着色器瓶颈就必须减面;
  • 过多的drawing call,过多的drawing call将导致较多的GPU时间浪费,因此要进行batch优化。
  • 不必要的顶点数据更新,那些从未被修改的顶点数组,应该用VBO存放在显存中,以避免每帧拷贝。有些硬件(例如SamsungTV2010)的性能受到顶点拷贝效率的影响较大;
  • 大量的动态顶点更新

有些顶点无可避免地需要更新,例如出于batch需要合并后的2D元件(Kingdoms & Lords)、粒子系统、Morph动画,要对他们进行优化,应该尽量减小顶点大小,可使用short类型。

  • interleaved vertex

将同一个顶点的位置,UV和法线等信息连续存储在一个结构类型的数组中,将提高渲染效率,可能的原因是提高GPU/CPU cache的效率。 在Apple的OpenGLES使用指南中有对这部分作详细介绍。

常见像素着色器性能问题

像素着色器优,主要是对计算方法进行优化。

  • 有些GPU处理半透明物件的能力偏差;
  • 有些GPU处理Alpha Test的能力偏差;
  • 较多的隐藏面,例如有些物件中包含了一些永远不可见的面,这些面可能没有多少顶点,但他们占用了较多的CPU计算时间。
  • 不必要的纹理切换,纹理切换同时意味着额外的drawing call
  1. 由于实体(solid,不带alpha通道)渲染可以通过深度缓冲区实现正确的遮挡关系,因此实体渲染不需要排序;同时这意味着固体内任何三角形以任何顺序渲染得到的画面是相同的。对这些实体部分进行排序,把使用相同纹理的实体合并到一个drawing call,可以显著提高渲染效率。这种合并经常在模型导出和渲染过程中进行。
  2. 合并纹理为大图,这样多个物件使用同一纹理,避免了切换,可以合并到一个drawingcall。
注意:影响drawing call是否能合并的因素不只是纹理。 

  • 当物件以较小的比例(屏幕上的尺寸大大小于纹理尺寸)渲染时,mipmap能有效提高渲染效率(Green Farm),这可能是由于GPU在用大纹理渲染较小多边形采样时cache命中率的问题。


0 0