Подтвердить что ты не робот

Как ограничить частоту кадров при использовании Android GLSurfaceView.RENDERMODE_CONTINUOUSLY?

У меня есть игра на С++, работающая через JNI в Android. Частота кадров варьируется от 20 до 45 кадров в секунду из-за сложности сцены. Все, что выше 30fps, глупо для игры; это просто сжигание батареи. Я хотел бы ограничить частоту кадров до 30 кадров в секунду.

  • Я могу переключиться на RENDERMODE_WHEN_DIRTY и использовать Timer или ScheduledThreadPoolExecutor для запросаRender(). Но это добавляет целый беспорядок дополнительных движущихся частей, которые могут или не могут работать последовательно и правильно.
  • Я пытался впрыскивать Thread.sleep(), когда все работает быстро, но это не работает вообще для небольших значений времени. И это может просто поддерживать события в очереди в любом случае, а не приостанавливать.

Есть ли способ "capFramerate()", скрывающийся в API? Любой надежный способ сделать это?

4b9b3361

Ответ 1

Решение от Марка почти хорошо, но не совсем корректно. Проблема в том, что сам обмен занимает значительное количество времени (особенно, если видеодрайвер является инструкцией кэширования). Поэтому вы должны принять это во внимание, или вы закончите с более низкой частотой кадров, чем хотелось бы. Так должно быть:

где-то в начале (например, конструктор):

startTime = System.currentTimeMillis();

то в цикле рендеринга:

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

Таким образом вы учтете время, необходимое для замены буферов и времени рисования кадра.

Ответ 2

При использовании GLSurfaceView вы выполняете рисунок в Renderer onDrawFrame, который обрабатывается в отдельном потоке с помощью GLSurfaceView. Просто убедитесь, что каждый вызов onDrawFrame занимает (1000/[кадров]) миллисекунд, в вашем случае что-то вроде 33 мс.

Для этого: (в вашем onDrawFrame)

  • Измерьте текущее время перед началом рисования с помощью System.currentTimeMillis(позвоните в startTime)
  • Выполните рисунок
  • Снова замерьте время (пусть назовет его endTime)
  • deltaT = endTime - starTime
  • если deltaT < 33, sleep (33-deltaT)

Что это.

Ответ 3

Ответ Fili выглядел великолепно, плохо печально ограничил FPS на моем устройстве Android до 25 FPS, хотя я и запросил 30. Я понял, что Thread.sleep() работает недостаточно точно и спит дольше, чем нужно.

Я нашел эту реализацию из проекта LWJGL для выполнения задания: https://github.com/LWJGL/lwjgl/blob/master/src/java/org/lwjgl/opengl/Sync.java

Ответ 4

Решение Fili терпит неудачу для некоторых людей, поэтому я подозреваю, что спал до момента сразу после следующего vsync, а не сразу. Я также чувствую, что перемещение сна до конца функции даст лучшие результаты, потому что там он может выставить текущий кадр перед следующим vsync, вместо того, чтобы пытаться компенсировать предыдущий. Thread.sleep() неточен, но, к счастью, нам нужно только то, чтобы он был точным до ближайшего периода vsync 1/60s. Код LWJGL tyrondis опубликовал ссылку, кажется слишком сложной для этой ситуации, она, вероятно, предназначена для тех случаев, когда vsync отключен или недоступен, что не должно быть в контексте этого вопроса.

Я бы попробовал что-то вроде этого:

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

Ответ 5

Вы также можете попытаться уменьшить приоритет потока от onSurfaceCreated():

Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);