Cocos2d-x在android开发下的帧数控制

自从人参第一次面试悲剧以后决定多补linux,顺手在linux下写点代码
于是就瞄准了cocos2dx这个框架,写个android下的小游戏
顺便给大家做下效率参考:
手机配置CPU 600Mhz,内存256M,显卡未知
以前用java下的opengles,大概每秒可以渲染16x16的纹理6000次
cocos2dx下面用的版本是2.1.4,自己做了个子弹池,全部用一个批次,在有纹理旋转的情况下每秒32000次
感觉30fps的话已经够用了!但是一接到手机上,发现fps怎么不受控制!?

一路追到Cocos2dxRenderer.java里一览究竟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void onDrawFrame(final GL10 gl) {
    /*
     * FPS controlling algorithm is not accurate, and it will slow down FPS
     * on some devices. So comment FPS controlling code.
     */

   
    /*
    final long nowInNanoSeconds = System.nanoTime();
    final long interval = nowInNanoSeconds - this.mLastTickInNanoSeconds;
    */


    // should render a frame when onDrawFrame() is called or there is a
    // "ghost"
    Cocos2dxRenderer.nativeRender();

    /*
    // fps controlling
    if (interval < Cocos2dxRenderer.sAnimationInterval) {
        try {
            // because we render it before, so we should sleep twice time interval
            Thread.sleep((Cocos2dxRenderer.sAnimationInterval - interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND);
        } catch (final Exception e) {
        }
    }

    this.mLastTickInNanoSeconds = nowInNanoSeconds;
    */

}

原来是帧数控制部分被官方注释掉了,他们给的原因是“不精确,在某些设备上还会拖慢速度”

当然除了动这个java文件,其实也有其他的解决方案,比如说干脆用一个while死循环完完全全地轮询过去,但是这种情况下的耗电和cpu占用都令人难以接受;
也可以直接在游戏逻辑部分用c++写自己的帧数控制,但是却丧失了可移植性(至少linux/win32下的精确帧数控制写法是不同的)
所以不能这么轻易就放弃,还是要在这里好好动手改一下。

首先要提出批评,貌似cocos2dx在这个android帧数控制上一直都有问题。
首先注意看Thread.sleep((Cocos2dxRenderer.sAnimationInterval - interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND); 这句话,在早期版本里他sleep的时间是目前的2倍(官方注释都被保留了下来作为证据= =),至于为啥要乘以2他也语焉不详了。
后来也有使用者发现了这个问题,于是去掉了乘以2,确实有所改良。(见此

不过现在的这个Cocos2dxRenderer.java里单纯去掉注释,还是不行,因为他interval(或是上一帧时间基准)的计算位置还是错的OTL……
给他挪好位置,在没什么负载的情况下,终于可以稳定在指定帧数上了。但是在有负载的情况下,却出现了拖慢的问题:
若是不启用帧数控制,则原本可同屏900子弹跑到35-40fps,若是启用帧数控制,连25帧都稳定不到了。
损耗出在哪里呢?我将计时换成了System.nanoTime(),已确定该函数损耗不高,但依旧不行。
问题出在sleep上。在java里,sleep本来就不是一个能够得到精确保证的计时函数,实际运行的时候,经过测试,精度至少是比15ms还要差的

所以我的解决方案是,先使用sleep进行等待在当前精度下比较保险的较短时间;对于剩余的部分,再采用while+轮询等待来补足
比如说,预计精度是15ms,当前帧要等待35ms,则sleep(20ms),在sleep醒来之后,可以预计此时时间已经过了20-35ms,但不会超过35ms,于是剩下的部分就用while等过去。
这样既能提高精度,又能够避免一直用循环等待而带来的高耗电和cpu占用。代码见下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final static long ACCURACYNANOSECONDSPERSECOND = NANOSECONDSPERMICROSECOND * 15; //accuracy
private long lastRenderingTime;
public void onDrawFrame(final GL10 gl) {
    Cocos2dxRenderer.nativeRender();

    long now = System.nanoTime();
    long renderingElapsedTime = now - lastRenderingTime;

    long timeToWait = Cocos2dxRenderer.sAnimationInterval - renderingElapsedTime;
    // try to sleep for a relative short (but safe) time according to ACCURACYNANOSECONDSPERSECOND
    try {
        if (timeToWait > ACCURACYNANOSECONDSPERSECOND) {
            Thread.sleep((timeToWait - ACCURACYNANOSECONDSPERSECOND);
        }
        } catch (InterruptedException e) {
        e.printStackTrace();
    }
    while(System.nanoTime() - now < timeToWait); // wait for the rest time that smaller than ACCURACYNANOSECONDSPERSECOND
    lastRenderingTime = System.nanoTime(); 
}

变量lastRenderingTime在onSurfaceCreated函数里初始化一下即可。
最后效果确实是提高了,至少可以稳定在28-30帧之间,勉强算是可以接受啦

发表评论

电子邮件地址不会被公开。

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>