web-dev-qa-db-fra.com

Comment limiter FPS dans une boucle avec C ++?

J'essaie de limiter les images par seconde dans une boucle qui effectue une vérification d'intersection, en utilisant C++ avec chrono et thread.

Voici mon code:

std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::chrono::system_clock::time_point lastFrame = std::chrono::system_clock::now();

while (true)
{
    // Maintain designated frequency of 5 Hz (200 ms per frame)
    now = std::chrono::system_clock::now();
    std::chrono::duration<double, std::milli> delta = now - lastFrame;
    lastFrame = now;

    if (delta.count() < 200.0)
    {
        std::chrono::duration<double, std::milli> delta_ms(200.0 - delta.count());
        auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
        std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
    }

    printf("Time: %f \n", delta.count());

    // Perform intersection test

}

Le problème que j'ai est que toutes les autres sorties de delta affichent des quantités minuscules, plutôt que les ~ 200 ms/trame que je vise:

Time: 199.253200
Time: 2.067700
Time: 199.420400
Time: 2.408100
Time: 199.494200
Time: 2.306200
Time: 199.586800
Time: 2.253400
Time: 199.864000
Time: 2.156500
Time: 199.293800
Time: 2.075500
Time: 201.787500
Time: 4.426600
Time: 197.304100
Time: 4.530500
Time: 198.457200
Time: 3.482000
Time: 198.365300
Time: 3.415400
Time: 198.467400
Time: 3.595000
Time: 199.730100
Time: 3.373400

Vous pensez pourquoi cela se produit?

24
Irongrave

Si vous pensez au fonctionnement de votre code, vous découvrirez qu'il fonctionne exactement comme vous l'avez écrit. Delta oscille en raison d'une erreur logique dans le code.

Voici ce qui se passe:

  • Nous commençons par delta == 0.
  • Parce que le delta est plus petit que 200, Vous codez sleeps 200 - delta(0) == 200 ms.
  • Maintenant, le delta lui-même devient proche de 200 (Parce que vous avez mesuré ce temps de sommeil ainsi qu'un travail réel) et vous dormez 200 - delta(200) == 0 ms.
  • Après cela, le cycle se répète.

Pour résoudre le problème, vous ne devez pas mesurer le temps de sommeil.

Voici comment cela peut être fait:

#include <iostream>
#include <cstdio>
#include <chrono>
#include <thread>

std::chrono::system_clock::time_point a = std::chrono::system_clock::now();
std::chrono::system_clock::time_point b = std::chrono::system_clock::now();

int main()
{
    while (true)
    {
        // Maintain designated frequency of 5 Hz (200 ms per frame)
        a = std::chrono::system_clock::now();
        std::chrono::duration<double, std::milli> work_time = a - b;

        if (work_time.count() < 200.0)
        {
            std::chrono::duration<double, std::milli> delta_ms(200.0 - work_time.count());
            auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
            std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
        }

        b = std::chrono::system_clock::now();
        std::chrono::duration<double, std::milli> sleep_time = b - a;

        // Your code here

        printf("Time: %f \n", (work_time + sleep_time).count());
    }
}

Ce code me donne une séquence régulière de deltas:

Time: 199.057206 
Time: 199.053581 
Time: 199.064718 
Time: 199.053515 
Time: 199.053307 
Time: 199.053415 
Time: 199.053164 
Time: 199.053511 
Time: 199.053280 
Time: 199.053283    
19
HolyBlackCat

Cela ressemble beaucoup à réponse de Galik , mais il conserve la syntaxe de la question de l'OP et ne descend pas dans l'API C. De plus, il crée une unité personnalisée pour la durée du cadre, ce qui, selon moi, est important pour la lisibilité:

#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>

int
main()
{
    using namespace std;
    using namespace std::chrono;

    using frames = duration<int64_t, ratio<1, 5>>;  // 5Hz
    auto nextFrame = system_clock::now();
    auto lastFrame = nextFrame - frames{1};;

    while (true)
    {
        // Perform intersection test

        this_thread::sleep_until(nextFrame);
        cout << "Time: "  // just for monitoring purposes
             << duration_cast<milliseconds>(system_clock::now() - lastFrame).count()
             << "ms\n";
        lastFrame = nextFrame;
        nextFrame += frames{1};
    }
}

Cela génère pour moi:

Time: 200ms
Time: 205ms
Time: 205ms
Time: 203ms
Time: 205ms
Time: 205ms
Time: 200ms
Time: 200ms
Time: 200ms
...

Éléments clés à noter:

  • Une façon concise de documenter 5 Hz: using frames = duration<int64_t, ratio<1, 5>>;
  • Utilisez sleep_until Au lieu de sleep_for, Ce qui prend en compte l'inconnu du temps qu'il faut pour faire le vrai travail.
  • Pas d'utilisation de .count() sauf pour les E/S et voici une bibliothèque pour s'en débarrasser .
  • Aucune conversion manuelle des unités (par exemple / 1000).
  • Pas d'unités à virgule flottante, pas qu'il n'y ait rien de mal à cela.
  • Besoin minimal de spécifier ou de dépendre d'unités explicites.

Avec l'ajout de la bibliothèque d'E/S de durée , voici comment le code ci-dessus serait modifié:

#include "chrono_io.h"
#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>

int
main()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;

    using frames = duration<int64_t, ratio<1, 5>>;  // 5Hz
    auto nextFrame = system_clock::now();
    auto lastFrame = nextFrame - frames{1};;

    while (true)
    {
        // Perform intersection test

        this_thread::sleep_until(nextFrame);
        // just for monitoring purposes
        cout << "Time: " << system_clock::now() - lastFrame << '\n';
        lastFrame = nextFrame;
        nextFrame += frames{1};
    }
}

La sortie serait différente selon la plate-forme (selon la "durée native" de system_clock). Sur ma plateforme, cela ressemble à ceci:

Time: 200042µs
Time: 205105µs
Time: 205107µs
Time: 200044µs
Time: 205105µs
Time: 200120µs
Time: 204307µs
Time: 205136µs
Time: 201978µs
...
12
Howard Hinnant

Je fais habituellement quelque chose comme ça:

#include <chrono>
#include <iostream>

int main()
{
    using clock = std::chrono::steady_clock;

    auto next_frame = clock::now();

    while(true)
    {
        next_frame += std::chrono::milliseconds(1000 / 5); // 5Hz

        // do stuff
        std::cout << std::time(0) << '\n'; // 5 for each second

        // wait for end of frame
        std::this_thread::sleep_until(next_frame);
    }
}

Sortie: (cinq pour chaque seconde valeur)

1470173964
1470173964
1470173964
1470173964
1470173964
1470173965
1470173965
1470173965
1470173965
1470173965
1470173966
1470173966
1470173966
1470173966
1470173966
8
Galik

Les temps de delta alternés proviennent d'un problème logique: vous ajoutez un retard à une trame en fonction de la durée de la trame précédente (en termes de calcul des durées de trame). Cela signifie qu'après une longue trame (~ 200 ms), vous n'appliquez pas de retard et obtenez une trame courte (quelques ms), qui déclenche alors un retard sur la trame suivante donnant une trame longue, et ainsi de suite.

4
Mike Dinsdale