How to limit framerate when using Android's GLSurfaceView.RENDERMODE_CONTINUOUSLY?

后端 未结 6 902
自闭症患者
自闭症患者 2020-12-12 16:53

I have a C++ game running through JNI in Android. The frame rate varies from about 20-45fps due to scene complexity. Anything above 30fps is silly for the game; it\'s just b

相关标签:
6条回答
  • 2020-12-12 17:14

    The solution from Mark is almost good, but not entirely correct. The problem is that the swap itself takes a considerable amount of time (especially if the video driver is caching instructions). Therefore you have to take that into account or you'll end with a lower frame rate than desired. So the thing should be:

    somewhere at the start (like the constructor):

    startTime = System.currentTimeMillis();
    

    then in the render loop:

    public void onDrawFrame(GL10 gl)
    {    
        endTime = System.currentTimeMillis();
        dt = endTime - startTime;
        if (dt < 33)
            Thread.Sleep(33 - dt);
        startTime = System.currentTimeMillis();
    
        UpdateGame(dt);
        RenderGame(gl);
    }
    

    This way you will take into account the time it takes to swap the buffers and the time to draw the frame.

    0 讨论(0)
  • 2020-12-12 17:15

    When using GLSurfaceView, you perform the drawing in your Renderer's onDrawFrame which is handled in a separate thread by the GLSurfaceView. Simply make sure that each call to onDrawFrame takes (1000/[frames]) milliseconds, in your case something like 33ms.

    To do this: (in your onDrawFrame)

    1. Measure the current time before your start drawing using System.currentTimeMillis (Let's call it startTime)
    2. Perform the drawing
    3. Measure time again (Let's call it endTime)
    4. deltaT = endTime - starTime
    5. if deltaT < 33, sleep (33-deltaT)

    That's it.

    0 讨论(0)
  • 2020-12-12 17:19

    Fili's solution is failing for some people, so I suspect it's sleeping until immediately after the next vsync instead of immediately before. I also feel that moving the sleep to the end of the function would give better results, because there it can pad out the current frame before the next vsync, instead of trying to compensate for the previous one. Thread.sleep() is inaccurate, but fortunately we only need it to be accurate to the nearest vsync period of 1/60s. The LWJGL code tyrondis posted a link to seems over-complicated for this situation, it's probably designed for when vsync is disabled or unavailable, which should not be the case in the context of this question.

    I would try something like this:

    private long lastTick = System.currentTimeMillis();
    
    public void onDrawFrame(GL10 gl)
    {
        UpdateGame(dt);
        RenderGame(gl);
    
        // Subtract 10 from the desired period of 33ms to make generous
        // allowance for overhead and inaccuracy; vsync will take up the slack
        long nextTick = lastTick + 23;
        long now;
        while ((now = System.currentTimeMillis()) < nextTick)
            Thread.sleep(nextTick - now);
        lastTick = now;
    }
    
    0 讨论(0)
  • 2020-12-12 17:25

    You may also try and reduce the thread priority from onSurfaceCreated():

    Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);
    
    0 讨论(0)
  • 2020-12-12 17:26

    If you don't want to rely on Thread.sleep, use the following

    double frameStartTime = (double) System.nanoTime()/1000000;
    // start time in milliseconds
    // using System.currentTimeMillis() is a bad idea
    // call this when you first start to draw
    
    int frameRate = 30;
    double frameInterval = (double) 1000/frame_rate;
    // 1s is 1000ms, ms is millisecond
    // 30 frame per seconds means one frame is 1s/30 = 1000ms/30
    
    public void onDrawFrame(GL10 gl)
    {
      double endTime = (double) System.nanoTime()/1000000;
      double elapsedTime = endTime - frameStartTime;
    
      if (elapsed >= frameInterval)
      {
        // call GLES20.glClear(...) here
    
        UpdateGame(elapsedTime);
        RenderGame(gl);
    
        frameStartTime += frameInterval;
      }
    }
    
    0 讨论(0)
  • 2020-12-12 17:33

    Fili's answer looked great to me, bad sadly limited the FPS on my Android device to 25 FPS, even though I requested 30. I figured out that Thread.sleep() works not accurately enough and sleeps longer than it should.

    I found this implementation from the LWJGL project to do the job: https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/opengl/Sync.java

    0 讨论(0)
提交回复
热议问题