Рубрики
3D мир Программирование

OpenGL: ограничение частоты кадров

В заметке рассматриваются способы ограничения частоты кадров для снижения нагрузки на графический процессор.

Введение

По умолчанию трехмерное приложение, использующее библиотеки OpenGL и GLFW3, ничего не ограничивает в потреблении ресурсов производительности, что может приводить к генерации большего числа кадров, чем это требуется пользователю с его настройками монитора. Например приложение может генерировать 200+ кадров в секунду, но монитор конечного пользователя будет ограничен частотой 60Гц (60 кадров в секунду) — данная ситуация порождает повышенные износ техники и потребление излишней электроэнергии.

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

  • вертикальная синхронизация средствами GLFW3;
  • ручной контроль за потоком рисования:
    • с ограничением по времени,
    • с использованием таймера.

Вертикальная синхронизация средствами GLFW3

Библиотека GLFW3 предлагает функциональность для ограничения частоты кадров, основываясь на кратности частоты обновления монитора. Проще говоря позволяет задать на какой вызов отрисовки монитора следует выводить содержимое экрана. Например если задать число 3, то кадр будет выводится каждое третье обновление монитора, а если задать число 1, то вывод будет производится каждый раз, когда монитор обновился.

Для задания такого ограничения следует вызвать функцию glfwSwapInterval, передав ей целое число, определяющее кратность обновлениям экрана.

Важное замечание: данную функцию следует вызывать после инициализации контекста OpenGL для выбранного окна (вызов функции glfwMakeContextCurrent)

Пример для вывода каждый раз, когда экран обновляется:

    glfwSwapInterval(1);

Если использовать вывод один раз в 30 кадров, при частоте обновления монитора в 60Гц, то окно будет перерисовываться 2 раза в секунду. Пример такого вызова:

    glfwSwapInterval(30);

Данную функциональность можно отключить, передав значение ноль:

    glfwSwapInterval(0);

Примечание: данная функция не требует вызова в цикле.

Ручной контроль за потоком рисования

Если разработчик хочет ограничить частоту кадров не прибегая к использованию вертикальной синхронизации, то ему доступны два пути:

  • ограничение через проверку порога времени ожидания;
  • ограничение через использование таймеров.

Оба способа обладают собственными недостатками, рассмотрим их подробнее.

Ограничение через проверку порога времени ожидания

Данный метод основывается на пустом прокручивании цикла рисования, пока порог времени ожидания не будет достигнут. Данный способ можно назвать «кустарным» таймером.

Добавим четыре переменных перед циклом рисования:

  • FPS — ограничение по числу кадров в секунду;
  • timeout — время на формирование одного кадра;
  • lastTime — время начала формирования последнего кадра;
  • currentTime — текущее время.

В цикле необходимо запросить текущее время с помощью функции glfwGetTime в переменную currentTime. Если разница текущего времени (currentTime) и времени начала формирования последнего кадра (lastTime) больше чем время на формирование одного кадра, то следует приступать к рисованию нового кадра, обновив значение в переменной lastTime.

Пример такого способа:

    const double FPS = 60.0; // Ограничение по числу кадров в секунду
    const double sigleFrameTime = 1.0 / FPS; // Время на формирование одного кадра  
    double lastTime = 0.0; // Время начала формирования последнего кадра
    double currentTime; // Текущее время
    // Пока не произойдет событие запроса закрытия окна
    while(!glfwWindowShouldClose(window))
    {
        currentTime = glfwGetTime(); // Получим текущее время
        // Если разница во времени больше или равна времени формирования одного кадра следует начать готовить новый
        if(currentTime - lastTime >= sigleFrameTime) 
        {
            lastTime = currentTime; // Обновим время начала формирования последнего кадра

            ... // Вызовы отрисовки

            // Представление содержимого буфера цепочки показа на окно
            glfwSwapBuffers(window);
            // Обработка системных событий
            glfwPollEvents();
        }
    }

Данный способ является достаточно точным в плане работы со временем, но имеет недостаток в виде пустого прокручивания цикла, что порождает лишние вызовы функции запроса текущего времени.

Ограничение через использование таймеров

Второй метод позволяет приостановить поток на заданное время, что разгружает процессор и оптимизирует энергопотребление.

Добавим пять переменных перед циклом рисования:

  • FPS — ограничение по числу кадров в секунду;
  • timeout — время на формирование одного кадра;
  • lastTime — время начала формирования последнего кадра;
  • currentTime — текущее время;
  • sleepTime — время сна.

В цикле необходимо запросить текущее время с помощью функции glfwGetTime в переменную currentTime. Если разница текущего времени (currentTime) и времени начала формирования последнего кадра (lastTime) меньше чем время на формирование одного кадра, то следует запустить таймер ожидания, после чего обновив значение в переменной lastTime.

Важное замечание: функция sleep точно соответствует частоте прерываний операционной системы, для Windows это 64 раза в секунду, что может оказать влияние на точность.

Пример работы с таймером:

    const double FPS = 60.0; // Ограничение по числу кадров в секунду
    const double sigleFrameTime  = 1.0 / FPS; // Время на формирование одного кадра  
    double lastTime = 0.0; // Время начала формирования последнего кадра
    double currentTime; // Текущее время
    unsigned int sleepTime; // Время сна
    // Пока не произойдет событие запроса закрытия окна
    while(!glfwWindowShouldClose(window))
    {        
        currentTime = glfwGetTime();
        if(currentTime - lastTime < sigleFrameTime)
        {
            sleepTime = (sigleFrameTime - (currentTime - lastTime)) * 1000.0;
        #ifdef _WIN32
            Sleep(sleepTime); // миллисекунды 
        #else
            usleep(sleepTime * 1000); // микросекунды
        #endif // _WIN32
        }
        lastTime = glfwGetTime();

        ... // Вызовы отрисовки

        // Представление содержимого буфера цепочки показа на окно
        glfwSwapBuffers(window);
        // Обработка системных событий
        glfwPollEvents();
    }

Данный способ хорошо обращается с процессорным временем, но имеет два недостатка:

  • неточность работы (иногда частота кадров падает до 59);
  • отсутствие кроссплатформенности (для каждой платформы необходимо вызвать свою реализацию функции sleep).

Примечание: автор знаком с std::this_thread, но он отсутствует в некоторых реализациях MinGW, с которым ведется работа в цикле заметок.

Заключение

В данной заметке рассмотрены различные способы контроля времени работы потока приложения, занимающегося рисованием. Для заметок будет использоваться вертикальная синхронизация средствами библиотеки GLFW3 в виду её простоты и компактности.

Репозитории заметок будут обновлены в соответствии с данным методом.

Один ответ к “OpenGL: ограничение частоты кадров”

Я столкнулся с тормозами clock_gettime в линуксе, так как там glfw3 его использует, хоть и через vDSO. На винде нет проблем от частого вызова получения времени. Короч я сделаю через слип чтоб часто не вызывать

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.