Android:了解 OnDrawFrame、FPS 和 VSync (OpenGL ES 2.0)

Posted

技术标签:

【中文标题】Android:了解 OnDrawFrame、FPS 和 VSync (OpenGL ES 2.0)【英文标题】:Android: Understanding OnDrawFrame, FPS and VSync (OpenGL ES 2.0) 【发布时间】:2016-07-21 19:36:29 【问题描述】:

一段时间以来,我在我的 android 游戏中遇到了正在运动的精灵的间歇性“断断续续”。这是一个非常简单的 2D OpenGL ES 2.0 游戏。 (这是一个持续存在的问题,我已经多次访问过)。

在我的游戏循环中,我有 2 个“计时器”——一个记录前一秒的帧数,另一个记录从当前 onDrawFrame 迭代结束到开始的时间(以毫秒为单位)下一个。

这是我发现的:

当不渲染任何东西时,我得到 60fps(大部分情况下),并且每次调用 onDrawFrame 时,它​​都会报告自己花费的时间超过 16.667 毫秒。现在,如果我渲染某些东西(不管是 1 个四边形还是 100 个四边形,结果都是一样的),我得到 60fps(大部分情况下),但现在,只有大约 20% 的 onDrawFrame 调用报告自己需要更长的时间距离上次调用超过 16.667 毫秒。

我真的不明白为什么会发生这种情况,首先,为什么当 onDrawFrame 没有做任何事情时,它会如此“缓慢”地调用 - 更重要的是,为什么任何 GL 调用(一个简单的四边形)仍然会onDrawFrame 调用之间的时间超过 16.667 毫秒(尽管频率要低得多)。

我应该说,当 onDrawFrame 报告从上次迭代起花费超过 16.667 毫秒时,它几乎总是伴随着 FPS 的下降(降至 58 或 59),但并非所有时间,有时,FPS 保持不变.反之,有时当 FPS 下降时,onDrawFrame 会在最后一次迭代完成后的 16.667 毫秒内被调用。

所以......

我正在尝试修复我的游戏循环并消除这些“口吃”——还有一些需要注意的事项:

当我进行方法分析时,它显示 glSwapBuffers,有时需要很长时间 当我执行 GL 跟踪时,它说大多数场景的渲染时间不到 1 毫秒,但有时奇数帧需要 3.5-4 毫秒 - 相同的场景。除了花费时间之外,没有任何变化 几乎每次丢帧或 onDrawFrame 报告长时间延迟(或两者兼有)时,都会出现视觉故障,但并非每次都如此。大的视觉故障似乎与多个“延迟”的 onDrawFrame 调用和/或丢帧相吻合。 我认为这不是场景复杂性问题,原因有两个:1) 即使我渲染了两次场景,也不会使问题变得更糟,我仍然在大多数情况下偶尔获得 60FPS drop,就像之前和 2)一样,即使我将场景裸露,我仍然会遇到问题。

我显然误解了一些东西,因此我们将不胜感激。

OnDrawFrame

@Override
public void onDrawFrame(GL10 gl) 

    startTime = System.nanoTime();        
    fps++;                        
    totalTime = System.nanoTime() - timeSinceLastCalled;    

    if (totalTime > 16667000)      
        Log.v("Logging","Time between onDrawFrame calls: " + (totalTime /(double)1000000));
    

    //Grab time
    newTime = System.currentTimeMillis() * 0.001;
    frameTime = newTime - currentTime; //Time the last frame took

    if (frameTime > 0.25)
        frameTime = 0.25;

    currentTime = newTime;
    accumulator += frameTime;

    while (accumulator >= dt)              
      saveGameState();
      updateLogic();
      accumulator -= dt;
    

    interpolation = (float) (accumulator / dt);

    Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);

    render(interpolation);

    if (startTime > lastSecond + 1000000000)           
        lastSecond = startTime;
        Log.v("Logging","fps: "+fps);
        fps=0;          
    

    endTime = startTime;
    timeSinceLastCalled = System.nanoTime();        

上面的这个游戏循环是这个出色的article 中的特色。

【问题讨论】:

这些故障是否与垃圾收集事件同时发生? @ReubenScratton,不,所有对象都是预先创建的,循环中没有分配,也没有 GC。这似乎是游戏循环本身的时间问题(我可能是错的,但似乎是这样)。谢谢 假设你使用一个专用的渲染线程,你如何在它和主线程之间同步状态? (FWIW 我从不使用专用渲染线程,更喜欢单线程,但我从未编写过复杂的游戏) 嗨@ReubenScratton,如果你的意思是我是否将我在单独线程上的逻辑更新为我渲染的逻辑,那么不,我在同一个线程(GL 线程)上进行渲染和逻辑更新 - 我从主 / UI 线程获取输入,但无论是否接收到输入,都会出现此问题。这是一个非常简单的 2D 游戏,所以我想让事情尽可能简单 - 谢谢! 可能只不过是“System.currentTimeMillis()*0.001”将一个非常大的数字乘以一个非常小的数字,这以不准确的结果而臭名昭著。 【参考方案1】:

一些想法:

不要使用System.currentTimeMillis() 来计时。它基于挂钟,可以通过网络更新。使用基于单调时钟的System.nanoTime()。 请参阅this appendix 了解有关游戏循环的一些说明。队列填充对很多事情都很好,但要明白您并没有完全使用 VSYNC,因此时间往往会不准确。 某些设备(尤其是基于 qcom SOC 的设备)在认为它们处于空闲状态时会降低 CPU 速度。始终在触摸屏上主动移动手指的同时进行计时。 如果你想调试帧率问题,你需要使用systrace。 traceview 分析在这里没有那么有用。

请参阅Grafika's“record GL app”Activity 以获取一个简单的 GLES 应用程序示例,该应用程序会丢帧,但会调整动画以使其很少被注意到。

【讨论】:

附录链接已损坏(嗯,只有哈希部分)-source.android.com/devices/graphics/arch-gameloops 现在似乎是正确的。 @Karu:他们将文档分成多个部分并重新排列了一些内容,因此有一堆链接断开的答案。 :-( 我已经更新了这个。如果您看到其他人,非常欢迎您直接编辑帖子。

以上是关于Android:了解 OnDrawFrame、FPS 和 VSync (OpenGL ES 2.0)的主要内容,如果未能解决你的问题,请参考以下文章

Android面试收集录 OpenGL ES

onDrawFrame、requestRender 和渲染线程? | OpenGL ES 2.0

在android游戏中放置主游戏循环的位置

如何在 Android 中保存相机拍摄的图片(应用 glsl 效果)?

OpenGL ES在android上画一个正方形

Android Gameloop (openGL ES 2.0) 的问题