Android: понимание OnDrawFrame, FPS и VSync (OpenGL ES 2.0)

Некоторое время я испытывал периодическое «заикание» спрайтов, которые находятся в движении в моей игре для Android. Это очень простая 2D-игра OpenGL ES 2.0. (Это постоянная проблема, к которой я возвращался много раз).

В моем игровом цикле у меня есть 2 «таймера» — один, который будет регистрировать количество кадров за предыдущую секунду, а другой, который подсчитывает время (в миллисекундах) от конца текущей итерации onDrawFrame до начала следующей.

Вот что я нашел:

Когда ничего не рендерится, я получаю 60 кадров в секунду (по большей части), и каждый раз, когда вызывается onDrawFrame, он сообщает, что занимает больше 16,667 мс. Теперь, если я что-то визуализирую (неважно, 1 квадрат или 100 квадратов, результат тот же), я получаю 60 кадров в секунду (по большей части), но теперь только около 20% вызовов onDrawFrame сообщают о том, что они занимают больше времени. чем 16,667 мс от последнего вызова.

Я действительно не понимаю, почему это происходит, во-первых, почему, когда onDrawFrame ничего не делает, он называется так "медленно" - и, что более важно, почему любой вызов GL (один простой quad), все еще делает время между onDrawFrame вызывает дольше 16,667 мс (хотя и гораздо реже).

Я должен сказать, что когда onDrawFrame сообщает о том, что прошло больше 16,667 мс с последней итерации, это почти всегда сопровождается падением FPS (до 58 или 59), но не всегда, иногда FPS остается постоянным. И наоборот, иногда, когда FPS падает, onDrawFrame вызывается в течение 16,667 мс после завершения последней итерации.

So......

Я пытаюсь исправить свой игровой цикл и устранить эти «заикания» — еще несколько вещей, на которые следует обратить внимание:

  • Когда я выполняю профилирование метода, он показывает glSwapBuffers, что иногда занимает много времени.
  • Когда я делаю GL Trace, большинство сцен, по его словам, рендерятся менее чем за 1 мс, но иногда нечетный кадр занимает 3,5-4 мс - та же сцена. Ничего не меняется, кроме времени, которое требуется
  • Почти каждый раз, когда кадр пропускается или onDrawFrame сообщает о большой задержке (или и то, и другое), возникает визуальный сбой, но не каждый раз. Большие визуальные сбои, по-видимому, совпадают с многочисленными «отложенными» вызовами onDrawFrame и/или пропущенными кадрами.
  • Я не думаю, что это проблема сложности сцены по двум причинам: 1) даже если я рендерю свою сцену дважды, это не усугубляет проблему, я по-прежнему по большей части получаю 60 кадров в секунду со случайным падением, просто как и раньше и 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();        
}

Этот игровой цикл описан в отличной статье.


person Zippy    schedule 21.07.2016    source источник
comment
Глюки совпадают с событиями сборки мусора?   -  person Reuben Scratton    schedule 24.07.2016
comment
@ReubenScratton, нет, все объекты создаются заранее, в цикле нет аллокации и нет GC. Кажется, это проблема синхронизации с самим игровым циклом (могу ошибаться, но похоже на то). Спасибо   -  person Zippy    schedule 24.07.2016
comment
Предполагая, что вы используете выделенный поток рендеринга, как вы синхронизируете состояние между ним и основным потоком? (FWIW я никогда не использую выделенный поток рендеринга и предпочитаю однопоточный, но ведь я никогда не писал сложную игру)   -  person Reuben Scratton    schedule 25.07.2016
comment
Привет @ReubenScratton, если вы имеете в виду, обновляю ли я свою логику в отдельном потоке до того, в котором я визуализирую, то нет, я выполняю рендеринг и обновление логики в одном потоке (поток GL) - я получаю ввод из основного потока / потока пользовательского интерфейса , но эта проблема возникает независимо от того, принимается ввод или нет. это действительно простая 2D-игра, поэтому я хотел, чтобы все было как можно проще — спасибо!   -  person Zippy    schedule 25.07.2016
comment
Может быть ничем иным, как System.currentTimeMillis()*0.001 умножает очень большое число на очень маленькое, что печально известно своими неточными результатами.   -  person Reuben Scratton    schedule 25.07.2016
comment
Откуда берется «dt» и каковы типичные значения? Может ли внутренний цикл, в котором он используется, выполняться гораздо чаще, чем вы думаете?   -  person Reuben Scratton    schedule 26.07.2016
comment
@ReubenScratton, dt составляет 1/60 (60 — это тики в секунду) — поскольку я использую фиксированный временной шаг — внутренний цикл while должен работать, когда цикл не может отобразиться вовремя и должен «наверстать упущенное» — следовательно, игра работает с постоянной скоростью 60 кадров в секунду, иногда она запускается дважды, что само по себе странно (поскольку рендеринг настолько прост, и я подтвердил, что onDrawFrame вызывается 60 раз в секунду (по большей части ).   -  person Zippy    schedule 26.07.2016
comment
Есть ли опечатка в вашем коде выше? if (frameTime > 0.25) на самом деле влияет только на оператор frameTime = 0.25;, однако ваш отступ предполагает обратное.   -  person c.s.    schedule 30.07.2016
comment
Хорошо замечено, это была ошибка форматирования @c.s. - исправлено   -  person Zippy    schedule 30.07.2016
comment
Имейте в виду, что дисплей не работает со скоростью ровно 60 кадров в секунду, и вы находитесь на устройстве, на котором работают другие вещи. Вы хотите, чтобы дельта-время основывалось на различиях временных меток от Choreographer, а не на произвольном фиксированном значении. Вы можете достаточно хорошо работать со временем начала onDrawFrame(), но, как упоминалось в статье, связанной с моим ответом, это основано на обратном давлении очереди, а не на VSYNC, и будет немного отличаться. Тем не менее, я использовал onDrawFrame() раза в Android Breakout (github.com/fadden/android-breakout ) и выглядело нормально.   -  person fadden    schedule 31.07.2016
comment
Вы говорите, что ваш кадр медленный, но вы измеряете время между onDrawFrame вызовами и фактически от конца одного кадра до начала следующего. Чтобы эта продолжительность составляла 1/60, ваш код не должен занимать нулевое время. Если ваш код занимает ненулевое время, это значение всегда должно быть ‹ 1/60. Тот факт, что вы наблюдаете большие значения (насколько именно большие?), означает, что система просто задерживает вызов вашего кода, а не то, что ваш кадр работает медленно. Ожидается, что некоторые кадры будут занимать больше времени (например, первый кадр, отрисованный GL, кажется медленнее), но я ожидаю, что сбой, вероятно, будет связан с неправильной интеграцией.   -  person c.s.    schedule 31.07.2016
comment
Что ж, @c.s., иногда кажется, что время занимает до 23+ мс, и когда это происходит, оно коррелирует с видимым задержкой - это может быть независимо от того, насколько сложна сцена. Что касается интеграции, я полагаю, вы имеете в виду, как я перемещаю свои спрайты? Я делаю так: yPos+=velocity*delta; а затем рассчитать, где рисовать, вот так: yDrawPos=yPos*height; (или ширина при расчете позиции X - это относится к ширине/высоте области экрана). Я также интерполирую во время рендеринга. Я использую фиксированную дельту, которая составляет 1/тикс_в_секунду (а тика_в_секунду равно 60). Спасибо   -  person Zippy    schedule 04.08.2016
comment
Под интеграцией да я подразумеваю комбинацию движения спрайта и интерполяции. Дело в том, что даже при длительности кадра 23 мс (это предполагает частоту кадров > 40 кадров в секунду) вы не должны наблюдать сбоев. Значит, виновато что-то другое. Если это значение относится к длительности между кадрами, это система, которая задерживает вызов вашего кода, что, как я подозреваю, сложнее устранить. Поэтому я бы сначала попытался доказать, что мой код верен, прежде чем выбирать, почему система задерживает путь (если, конечно, это не что-то очевидное, например, сборка мусора).   -  person c.s.    schedule 04.08.2016


Ответы (1)


Некоторые мысли:

  • Не используйте System.currentTimeMillis() для определения времени. Он основан на настенных часах, которые можно обновлять по сети. Используйте System.nanoTime(), основанный на монотонных часах.
  • См. это приложение для некоторых заметок об игровых циклах. Заполнение очередей подходит для многих вещей, но помните, что вы не совсем работаете с VSYNC, поэтому тайминги будут иметь тенденцию быть неточными.
  • Некоторые устройства (особенно основанные на SOC qcom) снижают скорость процессора, когда считают, что они простаивают. Всегда измеряйте время, активно перемещая палец по сенсорному экрану.
  • Если вы хотите отладить проблемы с частотой кадров, вам нужно использовать systrace. Профилирование traceview здесь не очень полезно.

См. Действие Grafika "record GL app" для примера простого приложения GLES, которое пропускает кадры, но корректирует анимация такая, что это редко заметно.

person fadden    schedule 21.07.2016
comment
Ссылка на приложение битая (ну, только хэш-часть) - source.android.com/ devices/graphics/arch-gameloops сейчас кажется правильным. - person Karu; 26.05.2017
comment
@Karu: Они разделили документ на несколько частей и кое-что изменили, поэтому есть куча ответов с неработающими ссылками. :-( Я обновил это. Если вы видите другие, вы можете редактировать сообщения напрямую. - person fadden; 26.05.2017