Как я могу реализовать точный (но переменный) лимит / ограничение FPS в моем приложении OpenGL? - PullRequest
9 голосов
/ 15 июля 2011

В настоящее время я работаю над приложением OpenGL для отображения пользователю нескольких трехмерных сфер, которые они могут вращать, перемещать и т. Д. При этом сложность здесь невелика, поэтому приложение работает надовольно высокая частота кадров (~ 500 кадров в секунду).

Очевидно, это излишне - даже 120 будет более чем достаточно, но моя проблема в том, что при запуске приложения с полной статистикой расходуется мой ЦП, что вызывает чрезмерное нагревание, энергопотребление и т. д. Я хочу позволить пользователю установить ограничение FPS, чтобы процессор не использовался чрезмерно, когда в этом нет необходимости.

Яработая с freeglut и C ++, и уже настроил обработку анимаций / событий для использования таймеров (используя glutTimerFunc).glutTimerFunc, однако, позволяет устанавливать целое количество миллисекунд - поэтому, если я хочу 120 FPS, самое близкое, что я могу получить, это разрешение (int)1000/120 = 8 ms, что равно 125 FPS (я знаю, что это незначительное количество, ноЯ все еще просто хочу установить предел FPS и получить именно тот FPS, если я знаю, что система может рендериться быстрее.

Более того, использование glutTimerFunc для ограничения FPS никогда не работает согласованно.Допустим, я ограничил свое приложение до 100 FPS, обычно оно никогда не поднималось выше 90-95 FPS.Опять же, я попытался выяснить разницу во времени между рендерингом / вычислениями, но затем он всегда превышает предел на 5-10 кадров в секунду (возможно, разрешение по таймеру).

Полагаю, лучшее сравнение здесь будетигра (например, Half Life 2) - вы устанавливаете свой предел FPS, и он всегда достигает именно этой суммы.Я знаю, что могу измерить дельты времени до и после рендеринга каждого кадра, а затем выполнить цикл, пока мне не понадобится рисовать следующий, но это не решает проблему 100% использования ЦП и не решает проблему разрешения времени.

Можно ли как-нибудь реализовать эффективный кроссплатформенный ограничитель / ограничение частоты кадров в моем приложении?Или, по-другому, есть ли кроссплатформенная (и с открытым исходным кодом) библиотека, которая реализует таймеры высокого разрешения и функции сна?

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

Edit # 2: Всем, кто рекомендует SDL (что я в итоге портировал свое приложение наSDL ), есть ли разница между использованием функции glutTimerFunc для запуска розыгрыша или SDL_Delay для ожидания между розыгрышами?В документации каждого из них упоминаются одни и те же предостережения, но я не был уверен, был ли один более или менее эффективным, чем другой.

Правка № 3: По сути, я пытаюсь выяснить, есть ли(простой способ) реализовать точный ограничитель FPS в моем приложении (опять же, как Half Life 2).Если это невозможно, я, скорее всего, переключусь на SDL (для меня более разумно использовать функцию задержки, а не использовать glutTimerFunc для вызова функции рендеринга каждые x миллисекунд).

Ответы [ 6 ]

4 голосов
/ 15 июля 2011

Я бы посоветовал вам использовать SDL. Я лично использую его для управления своими таймерами. Более того, он может ограничить ваш fps частотой обновления экрана (V-Sync) с SDL 1.3. Это позволяет вам ограничить использование ЦП при наилучшей производительности экрана (даже если у вас больше кадров, они не смогут отображаться, поскольку ваш экран не обновляется достаточно быстро).

Функция

SDL_GL_SetSwapInterval (1);

Если вам нужен код для таймеров, использующих SDL, вы можете увидеть это здесь:

мой класс таймера

Удачи:)

3 голосов
/ 22 сентября 2016

Я думаю, что хороший способ добиться этого, независимо от того, какую графическую библиотеку вы используете, состоит в том, чтобы в геймплее имелось одно измерение тактов с учетом каждого тика (мс).Таким образом, средняя частота кадров будет точно такой же, как в Half-Life 2. Надеюсь, что следующий фрагмент кода объяснит, о чем я говорю:

//FPS limit
unsigned int FPS = 120;

//double holding clocktime on last measurement
double clock = 0;

while (cont) {
    //double holding difference between clocktimes
    double deltaticks;

    //double holding the clocktime in this new frame
    double newclock;

    //do stuff, update stuff, render stuff...

    //measure clocktime of this frame
    //this function can be replaced by any function returning the time in ms
    //for example clock() from <time.h>
    newclock = SDL_GetTicks();

    //calculate clockticks missing until the next loop should be
    //done to achieve an avg framerate of FPS 
    // 1000 / 120 makes 8.333... ticks per frame
    deltaticks = 1000 / FPS - (newclock - clock);

    /* if there is an integral number of ticks missing then wait the
    remaining time
    SDL_Delay takes an integer of ms to delay the program like most delay
    functions do and can be replaced by any delay function */
    if (floor(deltaticks) > 0)
        SDL_Delay(deltaticks);

    //the clock measurement is now shifted forward in time by the amount
    //SDL_Delay waited and the fractional part that was not considered yet
    //aka deltaticks
    the fractional part is considered in the next frame
    if (deltaticks < -30) {
        /*dont try to compensate more than 30ms(a few frames) behind the
        framerate
        //when the limit is higher than the possible avg fps deltaticks
        would keep sinking without this 30ms limitation
        this ensures the fps even if the real possible fps is
        macroscopically inconsitent.*/
        clock = newclock - 30;
    } else {
        clock = newclock + deltaticks;
    }

    /* deltaticks can be negative when a frame took longer than it should
    have or the measured time the frame took was zero
    the next frame then won't be delayed by so long to compensate for the
    previous frame taking longer. */


    //do some more stuff, swap buffers for example:
    SDL_RendererPresent(renderer); //this is SDLs swap buffers function
}

Надеюсь, этот пример с SDL поможет.Важно измерять время только один раз за кадр, чтобы каждый кадр учитывался.Я рекомендую модулировать эту синхронизацию в функции, которая также делает ваш код более понятным.Этот фрагмент кода не имеет комментариев в том случае, если они просто раздражали вас в последнем:

unsigned int FPS = 120;

void renderPresent(SDL_Renderer * renderer) {
    static double clock = 0;
    double deltaticks;
    double newclock = SDL_GetTicks();

    deltaticks = 1000.0 / FPS - (newclock - clock);

    if (floor(deltaticks) > 0)
        SDL_Delay(deltaticks);

    if (deltaticks < -30) {
        clock = newclock - 30;
    } else {
        clock = newclock + deltaticks;
    }

    SDL_RenderPresent(renderer);
}

Теперь вы можете вызывать эту функцию в главном цикле вместо функции swapBuffer (SDL_RenderPresent(renderer) в SDL).В SDL вы должны убедиться, что флаг SDL_RENDERER_PRESENTVSYNC выключен.Эта функция использует глобальную переменную FPS, но вы можете подумать о других способах ее хранения.Я просто поместил все это в пространство имен моей библиотеки.


Этот метод ограничения частоты кадров обеспечивает точно желаемую среднюю частоту кадров, если нет больших различий во времени цикла для нескольких кадров из-за ограничения 30 мс дляdeltaticks.Требуется ограничение deltaticks.Когда предел FPS выше, чем фактическая частота кадров, deltaticks будет падать бесконечно.Кроме того, когда частота кадров снова поднимается выше предела FPS, код пытается компенсировать потерянное время путем немедленной визуализации каждого кадра, что приводит к огромной частоте кадров, пока deltaticks не вернется к нулю.Вы можете изменить 30 мс в соответствии с вашими потребностями, это только моя оценка.Я сделал пару тестов с Fraps.Он действительно работает с каждой мыслимой частотой кадров и дает прекрасные результаты из того, что я тестировал.


Я должен признать, что я кодировал это только вчера, поэтому вряд ли найдется какая-то ошибка.Я знаю, что этот вопрос задавали 5 лет назад, но данные ответы меня не утвердили.Также не стесняйтесь редактировать этот пост, так как он мой самый первый и, возможно, имеет недостатки.

РЕДАКТИРОВАТЬ: Мне стало известно, что SDL_Delay очень неточно в некоторых системах.Я слышал случай, когда он слишком сильно задерживается на Android.Это означает, что мой код может быть переносимым не на все ваши системы.

3 голосов
/ 15 июля 2011

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

Расширения интервал замены позволяют вашему приложению точно настраивать поведение V-синхронизации.Но в большинстве случаев просто включите V-синхронизацию в драйвере и разрешите блоку замены буфера, пока синхронизация не будет достаточной.

3 голосов
/ 15 июля 2011

Самый простой способ решить эту проблему - включить Vsync.Это то, что я делаю в большинстве игр, чтобы мой ноутбук не перегрелся.Пока вы убедитесь, что скорость вашего пути рендеринга не связана с другой логикой, это должно быть хорошо.

Существует функция glutGet (GLUT_ELAPSED_TIME), которая возвращает время с момента запуска в миллисекундах, но это, вероятно, все еще недостаточно быстро.

Простой способ - создать собственный метод таймера, который используетHighPerformanceQueryTimer для Windows и getTimeOfDay для систем POSIX.

Или вы всегда можете использовать функции таймера из SDL или SFML, которые в основном выполняют те же действия, что и выше.

1 голос
/ 02 января 2013

Я бы предложил использовать системные таймеры с точностью до мс (QueryPerformanceCounter, gettimeofday) для получения данных о времени.Это может помочь вам профилировать производительность в оптимизированных сборках релизов.

0 голосов
/ 20 мая 2018

Некоторая справочная информация:

  • SDL_Delay почти такой же, как Sleep / sleep / usleep / nanosleep, но ограничен миллисекундами в качестве параметра
  • Сон работает, полагаясь напланировщик системных потоков для продолжения вашего кода.
  • В зависимости от вашей ОС и аппаратного обеспечения планировщик может иметь более низкую частоту тиков, чем 1000 Гц, что приводит к более длительным временным интервалам, чем вы указали при вызове sleep, нет гарантии, чтобы получить желаемое время сна .
  • Вы можете попытаться изменить частоту планировщика.В Windows вы можете сделать это, вызвав timeBeginPeriod () .Для систем Linux linux этот ответ .
  • Даже если ваша ОС поддерживает частоту планировщика 1000 Гц, ваше оборудование может не работать, но большинство современного оборудования поддерживает.
  • Даже если ваш планировщикЧастота составляет 1000 Гц, сон может занять больше времени, если система занята процессами с более высоким приоритетом, но этого не должно происходить, если ваша система не находится под сверхвысокой нагрузкой.

Подводя итог, вы можете спатьв микросекундах на некоторых ядрах Linux без тиканья, но если вы заинтересованы в кроссплатформенном решении, вы должны попытаться увеличить частоту планировщика до 1000 Гц, чтобы обеспечить точность сна в большинстве случаев.

Чтобы решитьпроблема округления для 120FPS:

1000/120      = 8,333ms
(int)1000/120 = 8ms

Либо вы делаете занятое ожидание в течение 333 микросекунд и после этого спите в течение 8 мс.Который стоит некоторого процессорного времени, но супер точен.Или вы придерживаетесь подхода Неопа, спя иногда 8 мс, а иногда 9 мс, в среднем на 8 333 мс.Что намного эффективнее, но менее точно.

...