Как я могу реализовать точный (но переменный) предел FPS/cap в моем приложении OpenGL?

в настоящее время я работаю над приложением OpenGL для отображения нескольких 3D-сфер пользователю, которые они могут вращать, перемещать и т. д. Тем не менее, здесь не так много сложности, поэтому приложение работает с довольно высокой частотой кадров (~500 FPS).

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

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

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

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

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

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

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

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

6 ответов


Я бы предложил использовать системные таймеры Sub-ms precision (QueryPerformanceCounter, gettimeofday) для получения данных синхронизации. Они также могут помочь вам профилировать производительность в оптимизированных сборках выпуска.


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

функция

SDL_GL_SetSwapInterval (1);

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

мой класс timer

удачи :)


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

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

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

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


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

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


Я думаю, что хороший способ достичь этого, независимо от того, какую графическую библиотеку вы используете, - иметь одно измерение часов в gameloop, чтобы учитывать каждый тик (МС). Таким образом, средний fps будет точно таким же, как и в 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);
}

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


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


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

изменить: До моего сведения было доведено, что SDL_Delay очень очень неточно в некоторых системах. Я слышал случай, когда он слишком сильно задерживался на android. Это означает, что мой код не может быть переносим на все нужные вам системы.


справочная информация:

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

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

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

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

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