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

Гладкий игровой цикл Android


У меня проблемы с плавной прокруткой в ​​OpenGL (тестирование на SGS2 и ACE)
Я создал простые приложения - только фиксированную скорость горизонтальной прокрутки изображений или только одно изображение (плеер), движущееся по ускорителю, но это движение не гладко:-(
Я пробовал много разных кодов, но не удовлетворен...

сначала я пробовал работать с GLSurfaceView.RENDERMODE_CONTINUOUSLY, и я поместил весь код в onDrawFrame:

    public void onDrawFrame(GL10 gl) 
    {
      updateGame(gl, 0.017f);
      drawGame(gl); 
    }

это самый простой и абсолютно гладкий! - но это зависит от скорости аппаратного обеспечения (= бесполезно)

    public void onDrawFrame(GL10 gl) 
    { 
      frameTime = SystemClock.elapsedRealtime();        
      elapsedTime = (frameTime - lastTime) / 1000; 
      updateGame(gl, elapsedTime);
      drawGame(gl); 
      lastTime = frameTime; 
    }

это лучший из всех, но это не так гладко, как предыдущий, иногда щелкнуть


второй я попробовал GLSurfaceView.RENDERMODE_WHEN_DIRTY, в onDrawFrame у меня есть только чертежные объекты и этот код в отдельном потоке:

   while (true) 
   {
     updateGame(renderer, 0.017f);
     mGLSurfaceView.requestRender();
     next_game_tick += SKIP_TICKS;
     sleep_time = next_game_tick - System.currentTimeMillis();
     if (sleep_time >= 0) 
     {
       try 
       {
          Thread.sleep(sleep_time);
       } 
       catch (Exception e) {}
       }
       else 
       {
         Log.d("running behind: ", String.valueOf(sleep_time));
       }
     } 

это негласно, и это не проблема с "бегом за"


Моя цель - плавное движение изображения, как в первом примере кода выше.

Возможная ошибка в другом месте, я смотрю. Пожалуйста, может кто-нибудь помочь мне с этим?
Лучше использовать RENDERMODE_WHEN_DIRTY или RENDERMODE_CONTINUOUSLY?

Спасибо.

4b9b3361

Ответ 1

Я боролся с одной и той же проблемой в течение нескольких дней. Цикл выглядит гладко, без привязки к времени в Android, но как только он включает в себя любой тип "синхронизации времени", внешние факторы из управления разработкой android вводят серьезные разрывы в конечный результат.

В основном, эти факторы:

  • eglSwapInterval не реализован в Android, поэтому трудно узнать момент, когда аппаратное обеспечение отображает финальную ничью на экране (синхронизация аппаратного экрана).
  • Thread.sleep не является точным. Поток может спать больше или меньше запрошенного.
  • SystemClock.uptimeMillis() System.nanoTime(), System.currentTimeMillis() и другое измерение, связанное с измерением, неточно (точное).

Проблема не зависит от технологии рисования (рисунок, openGL 1.0/1.1 и 2.0) и метода игрового цикла (шаг фиксированного времени, интерполяция, шаг изменения времени). Как и вы, я пытался использовать Thread.sleep, сумасшедшие интерполяции, таймеры и т.д. Не имеет значения, что вы будете делать, мы не контролируем эти факторы.

В соответствии с множеством Q & A на этом сайте основными правилами для создания плавных непрерывных анимаций являются:

  • Уменьшите как минимум GC, удалив все запросы динамической памяти.
  • Рендеринг кадров так быстро, что аппаратное обеспечение может обрабатывать их (от 40 до 60 кадров в секунду в большинстве устройств Android).
  • Используйте фиксированные шаги времени с шагами интерполяции или изменения времени.
  • Оптимизировать физику обновления и рисовать процедуры, которые будут выполняться в относительном постоянном времени без изменения высокого пика.

Конечно, вы сделали много предыдущей работы, прежде чем опубликовать этот вопрос, оптимизируя ваши updateGame() и drawGame() (без заметного GC и относительного постоянного времени выполнения), чтобы получить гладкую анимацию в вашем основном цикле, поскольку вы упомянуть: "простой и абсолютный гладкость".

В вашем конкретном случае с переменной stepTime и без особых требований, чтобы быть в идеальной синхронизации с событиями realTime (например, музыкой), решение прост: "сгладьте шаг" Временная переменная ".
Решение работает с другими схемами игровых циклов (фиксированный временной шаг с переменным рендерингом) и легко переносит концепцию (сглаживает объем смещения, создаваемый updateGame и часы реального времени на нескольких кадрах.)

    // avoid GC in your threads. declare nonprimitive variables out of onDraw
    float smoothedDeltaRealTime_ms=17.5f; // initial value, Optionally you can save the new computed value (will change with each hardware) in Preferences to optimize the first drawing frames 
    float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
    long lastRealTimeMeasurement_ms; // temporal storage for last time measurement

    // smooth constant elements to play with
    static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
    static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)

    // sample with opengl. Works with canvas drawing: public void OnDraw(Canvas c)   
    public void onDrawFrame(GL10 gl){       
        updateGame(gl, smoothedDeltaRealTime_ms); // divide 1000 if your UpdateGame routine is waiting seconds instead mili-seconds.
        drawGame(gl);  

        // Moving average calc
        long currTimePick_ms=SystemClock.uptimeMillis();
        float realTimeElapsed_ms;
        if (lastRealTimeMeasurement_ms>0){
        realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
        } else {
                 realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
        }
        movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;

         // Calc a better aproximation for smooth stepTime
        smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;

        lastRealTimeMeasurement_ms=currTimePick_ms;
    }

    // Optional: check if the smoothedDeltaRealTIme_ms is too different from original and save it in Permanent preferences for further use.

Для схемы с фиксированным временным шагом может быть реализовано промежуточное updateGame для улучшения результатов:

float totalVirtualRealTime_ms=0;
float speedAdjustments_ms=0; // to introduce a virtual Time for the animation (reduce or increase animation speed)
float totalAnimationTime_ms=0;
float fixedStepAnimation_ms=20; // 20ms for a 50FPS descriptive animation
int currVirtualAnimationFrame=0; // useful if the updateGameFixedStep routine ask for a frame number

private void updateGame(){
    totalVirtualRealTime_ms+=smoothedDeltaRealTime_ms + speedAdjustments_ms;

    while (totalVirtualRealTime_ms> totalAnimationTime_ms){
        totalAnimationTime_ms+=fixedStepAnimation_ms;
        currVirtualAnimationFrame++;
        // original updateGame with fixed step                
        updateGameFixedStep(currVirtualAnimationFrame);
    }


    float interpolationRatio=(totalAnimationTime_ms-totalVirtualRealTime_ms)/fixedStepAnimation_ms;
    Interpolation(interpolationRatio);
}

Протестировано с рисунком canvas и openGlES10 со следующими устройствами: SG SII (57 FPS), SG Примечание (57 FPS), вкладка SG (60 FPS), ненавязчивый медленный эмулятор Android 2.3 (43 FPS), работающий в Windows XP (8 FPS). Испытательная платформа рисует около 45 объектов + 1 огромный фон (текстура из исходного изображения 70MP), перемещаясь по пути, указанному в реальных физических параметрах (км/ч и G), без всплесков или щелчков между несколькими устройствами (ну, 8 FPS на эмуляторе не выглядит хорошо, но его поток с постоянной скоростью, как ожидалось)

Проверить Графики того, как андроид сообщает время. В некоторых случаях Android сообщает о большом дельта-времени и только в следующем цикле он меньше среднего, что означает смещение при чтении значения realTime.

enter image description here

более подробно: enter image description here

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

System.currentTimeMillis vs System.nanoTime

Действительно ли метод System.currentTimeMillis() действительно возвращает текущее время?