Как ограничить частоту кадров при использовании Android GLSurfaceView.RENDERMODE_CONTINUOUSLY? - PullRequest
21 голосов
/ 23 января 2011

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

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

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

Ответы [ 6 ]

38 голосов
/ 10 июня 2011

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

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

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

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

9 голосов
/ 23 января 2011

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

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

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

Вот и все.

2 голосов
/ 01 апреля 2016

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

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

0 голосов
/ 09 сентября 2018

Если вы не хотите полагаться на Thread.sleep, используйте следующее

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 голосов
/ 18 января 2017

Решение Fili не работает для некоторых людей, поэтому я подозреваю, что оно спит до момента, следующего за следующей vsync, а не сразу.Я также чувствую, что перемещение режима сна в конец функции даст лучшие результаты, потому что там он может дополнить текущий кадр перед следующим vsync, вместо того, чтобы пытаться компенсировать предыдущий.Thread.sleep () неточен, но, к счастью, нам нужно только, чтобы он был точным с точностью до ближайшего периода vsync 1/60 с.Код 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;
}
0 голосов
/ 31 августа 2011

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

Process.setThreadPriority(Process.THREAD_PRIORITY_LESS_FAVORABLE);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...