Пару вопросов по SFML

Я решил познакомиться с библиотекой SFML. Написал простую программу окна. Код:

#include <SFML\Graphics.hpp>
#include <sstream>

using namespace sf;
using namespace std;

void Update(RenderWindow& MainWindow);  // Обновление
void Render(RenderWindow& MainWindow);  // Рендеринг

int main()
{
    // Создать главное окно
    RenderWindow MainWindow(VideoMode(1024, 768), "Engine", Style::Close);

    // Ограничить FPS до 60
    MainWindow.setFramerateLimit(60);

    // Часы
    Clock clock;

    // Основной цикл
    while (MainWindow.isOpen())
    {
        float DeltaTime = clock.restart().asSeconds();

        Update(MainWindow); // Обновление
        Render(MainWindow); // Рендеринг

        // Вывод FPS на заголовок окна
        stringstream TitleString;
        TitleString << "FPS: " << 1.f / DeltaTime;
        MainWindow.setTitle(TitleString.str());
    }

    return 0;
}

// Обновление
void Update(RenderWindow& MainWindow)
{
    // События
    Event event;

    // Обработка событий (нажатие кнопок, закрытие окна и т.д.)
    while (MainWindow.pollEvent(event))
    {
        // Закрыть окно если событие "Закрыть окно"
        if (event.type == Event::Closed)
            MainWindow.close();
    }

    // ...

}

// Рендеринг
void Render(RenderWindow& MainWindow)
{
    MainWindow.clear();     // Очистить

    // ...

    MainWindow.display();   // Отобразить
}

После запуска программа значительно нагружает CPU и GPU (соответственно ~10% и ~50%). Возможным решением этой проблемы является ограничение FPS через setFramerateLimit (поставил 60). Это действительно помогает снизить нагрузку на CPU и GPU. Но нагрузка на GPU всё равно остается достаточно высоким (~10%). Дополнение к этому FPS иногда перескакивает за 60 (до 61-62), хотя стоит ограничение.

Как решить эти проблемы?


Ответы (1 шт):

Автор решения: sam0delk1n

Ваш счётчик FPS по сути измеряет время одного текущего кадра и пытается экстраполировать результат на всю секунду. Лучше считать количество отрисованных по факту FPS в течении одной секунды и выводить это значение.

Например:

int main() {
    // Создать главное окно
    RenderWindow MainWindow(VideoMode(1024, 768), "Engine", Style::Close);

    // Ограничить FPS до 60
    MainWindow.setFramerateLimit(60);

    Clock        clock;   // Часы
    unsigned int fps = 0; // Счётчик кадров в секунду

    // Основной цикл
    while (MainWindow.isOpen()) {
        Update(MainWindow); // Обновление
        Render(MainWindow); // Рендеринг

        ++fps; // Прибавляем один отрисованный кадр

        // Если прошла одна секунда или больше ...
        if (sf::seconds(1) <= clock.getElapsedTime()) {

            // Выводим значение FPS в шапку окна
            stringstream TitleString;
            TitleString << "FPS: " << fps;
            MainWindow.setTitle(TitleString.str());

            clock.restart(); // Перезапускаем часы
            fps = 0;         // Обнуляем счётчик
       }
    }

    return 0;
}

Отклонение на +/- 1 FPS это нормально, это связано с округлением целочисленного значения счётчика в ту или иную сторону.

Нужно отметить что функция sf::Window::setFramerateLimit основана на sf::sleep, точность которой зависит от таймера и планировщика операционной системы и может давать неточные результаты.

setFramerateLimit можно заменить на sf::Window::setVerticalSyncEnabled чтобы обновление изображения соответствовало частоте развёртки экрана. Однако задача вертикальной синхронизации убирать tearing (разрыв изображения), а не ограничивать FPS, поэтому его значение не обязано быть фиксированным и зависит от настроек видеодрайвера и оборудования.

Одновременно применять setVerticalSyncEnabled(true) и setFramerateLimit документацией не рекомендуется.

Стоит добавить что применение std::chrono может измерять время точнее. Тогда управлять потоком можно с помощью std::this_thread::sleep_for или std::this_thread::sleep_until.

Я выставил ограничение 10 FPS и у меня потребление GPU сократилось до 1%. Можно попробовать снизить разрешение окна или рисовать меньше контента. На SFML по другому влиять на производительность сложно. Нужны более низкоуровневые GAPI, например OpenGL или Vulkan.

→ Ссылка