Как исправить петлю 60 кадров в секунду на дисплее с несовершенной частотой 60 Гц (59,92 Гц)? - PullRequest
2 голосов
/ 04 ноября 2019

Я только начал изучать программирование игр, я решил использовать игровой цикл с фиксированным временным шагом, поскольку он прост и понятен.

В основном так:

    while (game_is_running) {
        input();
        update();
        render();
    }

Когда VSyncвключен, он работает со скоростью моего дисплея, но я хочу ограничить его скорость до 60 FPS даже на мониторах с более высокой частотой обновления, поэтому я добавил пропуск кадров и ограничитель кадров:

static void Main()
{
    long frame_count = 0;
    long tick_frame_end;

    Stopwatch stopwatch = new Stopwatch();
    WaitForVSync();
    stopwatch.Start(); // hoping to get in sync with display

    while (game_is_running) {
        frame_count++;
        tick_frame_end = GetEndTick(frame_count);

        if (stopwatch.ElapsedTicks > tick_frame_end) { // falling behind
            continue; // skip this frame
        }

        input();
        update(tick_frame_end);
        // render(); // render here when VSync is enabled

        while (stopwatch.ElapsedTicks < tick_frame_end) {
            // Busy waiting, in real code I used Spinwait to avoid 100% cpu usage
        }
        render(); // I moved it here because I turned off VSync and I want render to happen at a constant rate
    }
}

static long GetEndTick(long frame_count) {
    return (long)((double)frame_count * Stopwatch.Frequency / 60); // Target FPS: 60
}

Когда VSync выключен, он может работать с постоянной частотой 30, 60, 120 (или любым значением между ними) FPS. Я был доволен результатом, но как только я снова включил VSync (и установил FPS на 60), я заметил, что пропускается много кадров.

Я могу работать стабильно 120FPS, когда VSync выключен с неодин кадр отброшен, но при работе на 60FPS VSync у меня заметно количество пропущенных кадров. Мне потребовалось некоторое время, чтобы найти причину:

Мой монитор, который , как мне показалось, работал на частоте 60 Гц, на самом деле работает на частоте 59,920 Гц (протестировано с * 1016)*

Со временем мое внутреннее время кадра и время монитора будут становиться все более несинхронными, вызывая много неожиданных пропаданий кадров. Это полностью нарушает мою функцию GetEndTick(), которая в основном нарушает все: логику пропуска кадров, ограничитель частоты кадров и, самое главное, update(tick_frame_end) не работает.

Итак, вопросы:

1. Как узнать время окончания текущего кадра?

В моем коде выше время окончания рассчитывается исходя из предположения, что в каждую секунду есть 60 четных кадров. Если это правда, мое внутреннее время кадра может не совпадать идеально с временем кадра дисплея, но все равнобыть в синхронизации, что достаточно полезно.

(Все было рассчитано на основе отметки времени окончания кадра, идея в том, что я генерирую изображение на основе времени fконец rame, то есть момент до того, как на экране отобразится следующий кадр.)

Я попытался записать время в начале цикла (или сразу после рендеринга последнего кадра) и добавить к нему время, равное 1 кадру- Не работает , поскольку записанное время не является надежным из-за загрузки системы, фоновых процессов и многих других вещей. Также мне понадобится новый метод, чтобы определить, какой кадр является текущим.

2. Если (1.) невозможно, как я могу исправить свой игровой цикл?

Я прочитал печально известный Fix Your Timestep! от Гленна Фидлера, но все же об игрефизика не графическая и не дает мне ответа на первый вопрос.

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