自从人参第一次面试悲剧以后决定多补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帧之间,勉强算是可以接受啦