Android: Understanding OnDrawFrame, FPS and VSync

2020-06-01 07:59发布

问题:

For a while now I've experienced an intermittent 'stuttering' of the sprites that are in motion within my Android Game. It's a fiarly simple 2D OpenGL ES 2.0 game. (This is an ongoing problem which I have re-visited many times).

In my game loop, I have 2 'timers' - one which will log the number of frames in the previous second, and another which counts the time (in Milliseconds) from the end of the current onDrawFrame iteration to the start of the next.

This is what I've found:

When not rendering anything, I get 60fps (for the most part), and every time onDrawFrame is called, it reports itself as taking longer that 16.667ms. Now, If I render something (doesn't matter if it's 1 quad or 100 quads, the result is the same), I get 60fps (for the most part) but now, only about 20% of onDrawFrame calls report themselves as taking longer than 16.667ms from the last call.

I don't really understand why this happens, firstly, why, when onDrawFrame isn't doing anything, is it called so 'slowly' - and more importantly, why does any GL call (one simple quad), still make the time between onDrawFrame calls longer than 16.667ms (albeit much less frequently).

I should say that when onDrawFrame reports taking longer than 16.667ms from the last iteration, it is almost always accompanied by a drop in FPS (to 58 or 59), but not all of the time, sometimes, the FPS stays constant. And conversely, sometimes when the FPS drops, onDrawFrame is called within 16.667ms of the last iteration completing.

So......

I'm trying to fix my game-loop and eradicate these 'stutters' - some other things to note:

  • When I do method profiling, it shows glSwapBuffers, sometimes taking a long time
  • When I do a GL Trace, most scenes its says renders in less than 1ms, but sometimes the odd frame takes 3.5-4ms - same scene. Nothing changes apart from the time it takes
  • Almost every time a frame is dropped, or onDrawFrame reports a long delay (or both), there is a visual glitch, but not every time. Big visual glitches seems to coincide with multiple 'delayed' onDrawFrame calls and /or dropped frames.
  • I don't think this is a scene complexity issue for 2 reasons: 1) even if I render my scene twice, it doesn't make the problem any worse, I still for the most part, get 60FPS with the occasional drop, just as before and 2), even if I strip the scene bare, I still get the problem.

I obviously am misunderstanding something, so a push in the right direction would be appreciated.

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();        
}

This game loop above is the one featured in this excellent article.

回答1:

Some thoughts:

  • Don't use System.currentTimeMillis() for timing things. It's based on the wall clock, which can be updated by the network. Use System.nanoTime(), which is based off the monotonic clock.
  • See this appendix for some notes on game loops. Queue-stuffing is fine for many things, but understand that you're not exactly working off of VSYNC, so timings will tend to be inaccurate.
  • Some devices (notably those based on qcom SOCs) reduce CPU speed when they think they're idle. Always take timings while actively moving your finger around on the touch screen.
  • If you want to debug frame rate issues you need to be using systrace. The traceview profiling isn't that useful here.

See Grafika's "record GL app" Activity for an example of a simple GLES app that drops frames, but adjusts the animation such that it's rarely noticeable.