67 votes

Pourquoi certains jeux anciens tournent-ils beaucoup trop vite sur du matériel moderne ?

J'ai quelques vieux programmes que j'ai retirés d'un ordinateur Windows du début des années 90 et j'ai essayé de les faire fonctionner sur un ordinateur relativement moderne. Il est intéressant de noter qu'ils fonctionnaient à une vitesse fulgurante - non, pas le genre 60 images par seconde, mais plutôt le genre oh-my-god-the-character-is-walking-at-the-speed-of-sound. J'appuyais sur une flèche et le sprite du personnage traversait l'écran beaucoup plus rapidement que d'habitude. La progression du temps dans le jeu était beaucoup plus rapide qu'elle ne le devrait. Il existe même des programmes conçus pour ralentir votre CPU pour que ces jeux soient réellement jouables.

J'ai entendu dire que cela était lié au fait que le jeu dépendait des cycles du processeur, ou quelque chose comme ça. Mes questions sont les suivantes :

  • Pourquoi les anciens jeux font-ils cela, et comment ont-ils pu s'en sortir ?
  • Comment les jeux plus récents no fait cela et fonctionne indépendamment de la fréquence du CPU ?

55voto

Chochos Points 3364

Je crois qu'ils supposaient que l'horloge du système fonctionnerait à un rythme spécifique et qu'ils liaient leurs minuteries internes à ce rythme. La plupart de ces jeux ont probablement fonctionné sous DOS, et étaient mode réel (avec un accès matériel complet et direct) et supposé que vous exécutez une iirc 4,77 MHz pour les PC et tout processeur standard utilisé par ce modèle pour d'autres systèmes comme l'Amiga.

Ils ont également pris des raccourcis astucieux basés sur ces hypothèses, notamment en économisant un tout petit peu de ressources en n'écrivant pas de boucles de synchronisation internes dans le programme. Ils utilisaient également toute la puissance du processeur, ce qui était une bonne idée à l'époque des puces lentes et souvent refroidies passivement !

Au départ, une façon de contourner la différence de vitesse du processeur était le bon vieil Bouton turbo (ce qui ralentissait votre système). Les applications modernes sont en mode protégé et le système d'exploitation a tendance à gérer les ressources. permettre une application DOS (qui s'exécute de toute façon dans NTVDM sur un système 32 bits) pour utiliser tout le processeur dans de nombreux cas. En bref, les systèmes d'exploitation sont devenus plus intelligents, tout comme les API.

Fortement inspiré de ce guide sur Oldskool PC là où la logique et la mémoire m'ont fait défaut - c'est un excellent livre, qui explique probablement plus en profondeur le "pourquoi".

Des trucs comme CPUkiller utilisent autant de ressources que possible pour "ralentir" votre système, ce qui est inefficace. Vous feriez mieux d'utiliser DOSBox pour gérer la vitesse d'horloge que votre application voit.

28voto

Gizmo Points 1849

En complément de la réponse de Journeyman Geek (parce que mon édition a été rejetée) pour les personnes intéressées par la partie codage/point de vue du développeur :

Du point de vue des programmeurs, pour ceux que cela intéresse, l'époque du DOS était une époque où chaque tic du processeur était important, les programmeurs devaient donc garder le code aussi rapide que possible.

Un scénario typique où tout programme s'exécutera à la vitesse maximale du CPU est ce simple pseudo C :

int main()
{
    while(true)
    {

    }
}

Cela durera toujours.

Maintenant, transformons cet extrait de code en un jeu pseudo-DOS :

int main()
{
    bool GameRunning = true;

    while(GameRunning)
    {
        ProcessUserMouseAndKeyboardInput();
        ProcessGamePhysics();
        DrawGameOnScreen();

        // close game
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

À moins que le DrawGameOnScreen utilise le double buffering/V-sync (ce qui était assez coûteux à l'époque où les jeux DOS étaient réalisés), le jeu fonctionnera à la vitesse maximale du CPU. Sur un portable i7 moderne, cette fonction s'exécuterait entre 1 000 000 et 5 000 000 de fois par seconde (selon la configuration de l'ordinateur portable et l'utilisation actuelle du CPU).

Cela signifie que si je pouvais faire fonctionner n'importe quel jeu DOS sur mon processeur moderne dans mon Windows 64 bits, je pourrais obtenir plus de mille (1000 !) FPS - ce qui est trop rapide pour qu'un humain puisse jouer - si le traitement physique "suppose" qu'il fonctionne entre 50 et 60 FPS.

Ce que les développeurs modernes (peuvent) faire

  • Activer V-Sync dans le jeu (non disponible pour les applications fenêtrées* - c'est-à-dire uniquement disponible dans les applications plein écran)
  • Mesure le temps écoulé depuis la dernière mise à jour et ajuste le traitement physique en conséquence, ce qui permet au jeu/programme de fonctionner à la même vitesse, quelle que soit la vitesse du CPU.
  • Limiter le framerate de façon programmatique

* En fonction de la configuration de la carte graphique, du pilote et du système d'exploitation, il est possible d'obtenir des informations sur l'état du système. かもしれません être possible.

Pour la première option, je ne montrerai pas d'exemples car il ne s'agit pas vraiment de "programmation" - il s'agit simplement d'utiliser les fonctions graphiques.

Quant aux deux autres options, je vous montrerai les extraits de code et les explications correspondantes.

Mesurer le temps écoulé depuis la dernière mise à jour

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;

    while(GameRunning)
    {
        TimeDifference = GetCurrentTime() - LastTick;
        LastTick = GetCurrentTime();

        // process movements based on time passed and keys pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        // pass the time difference to the physics engine, so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);

        DrawGameOnScreen();

        // close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

Ici, vous pouvez voir que l'entrée de l'utilisateur et la physique prennent en compte la différence de temps, et pourtant vous pouvez obtenir plus de 1000 FPS à l'écran parce que la boucle tourne aussi vite que possible. Comme le moteur physique sait combien de temps s'est écoulé, il n'a pas besoin de dépendre de "pas d'hypothèses" ou d'"une certaine fréquence", de sorte que le jeu fonctionnera au même framerate sur n'importe quel CPU.

Limiter le framerate de manière programmatique

Ce que les développeurs peuvent faire pour limiter le framerate à, par exemple, 30 FPS n'est pas plus difficile - il suffit de jeter un coup d'œil :

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;

    double DESIRED_FPS = 30;

    // how many milliseconds need to pass before the next draw so we get the framerate we want
    double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;

    // note to geek programmers: this is pseudo code, so I don't care about variable types and return types
    double LastDraw = GetCurrentTime();

    while(GameRunning)
    {
        TimeDifference = GetCurrentTime() - LastTick;
        LastTick = GetCurrentTime();

        // process movements based on time passed and keys pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        // pass the time difference to the physics engine, so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);

        // if certain number of milliseconds pass...
        if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
        {
            // draw our game
            DrawGameOnScreen();

            // and save when we last drew the game
            LastDraw = LastTick;
        }

        // close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }
    }
}

Le programme compte les millisecondes passées et, lorsqu'un certain nombre de millisecondes est atteint (33 ms), il redessine l'écran du jeu, appliquant ainsi un taux de rafraîchissement proche de 30 FPS.

De même, le développeur peut choisir de limiter tous à 30 FPS en modifiant légèrement le code ci-dessus :

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;

    double DESIRED_FPS = 30;

    // how many milliseconds need to pass before the next draw so we get the framerate we want
    double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;

    // note to geek programmers: this is pseudo code, so I don't care about variable types and return types
    double LastDraw = GetCurrentTime();

    while(GameRunning)
    {
        LastTick = GetCurrentTime();
        TimeDifference = LastTick - LastDraw;

        // if certain number of milliseconds pass...
        if(TimeDifference >= TimeToPassBeforeNextDraw)
        {
            // process movements based on time passed and keys pressed
            ProcessUserMouseAndKeyboardInput(TimeDifference);

            // pass the time difference to the physics engine, so it can calculate anything time-based
            ProcessGamePhysics(TimeDifference);

            // draw our game
            DrawGameOnScreen();

            // and save when we last drew the game
            LastDraw = LastTick;

            // close game if escape is pressed
            if(Pressed(KEY_ESCAPE))
            {
                GameRunning = false;
            }
        }
    }
}

Autres alternatives

Il y a quelques autres méthodes, et certaines d'entre elles me font vraiment horreur. Par exemple, l'utilisation de sleep(NumberOfMilliseconds) .

Je sais que c'est une méthode pour limiter le framerate, mais que se passe-t-il lorsque le traitement de votre jeu prend 3 millisecondes ou plus et que vous exécutez ensuite le sleep ? Il en résultera un framerate plus faible que celui qui ne prend que 3 millisecondes. sleep() devrait causer.

Prenons, par exemple, un temps de sommeil de 16 ms. Ainsi, le programme fonctionnera à 60 Hz. Maintenant, disons que le traitement des données, l'entrée, le dessin et tout le reste prend 5 millisecondes. Cela nous amène à 21 millisecondes pour une boucle, ce qui donne un peu moins de 50 Hz, alors que vous pourriez facilement être encore à 60 Hz, mais à cause du sommeil codé en dur, c'est impossible.

Une solution serait d'effectuer un "sommeil adaptatif", sous la forme d'une mesure du temps de traitement et d'une déduction du temps de traitement du sommeil souhaité, ce qui permettrait de corriger notre "bug" :

int main()
{
    bool GameRunning = true;
    long long LastTick = GetCurrentTime();
    long long TimeDifference;
    long long NeededSleep;

    while(GameRunning)
    {
        TimeDifference = GetCurrentTime() - LastTick;
        LastTick = GetCurrentTime();

        // process movements based on time passed and keys pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

        // pass the time difference to the physics engine, so it can calculate anything time-based
        ProcessGamePhysics(TimeDifference);

        // draw our game
        DrawGameOnScreen();

        // close game if escape is pressed
        if(Pressed(KEY_ESCAPE))
        {
            GameRunning = false;
        }

        NeededSleep = 33 - (GetCurrentTime() - LastTick);
        if(NeededSleep > 0)
        {
            Sleep(NeededSleep);
        }
    }
}

17voto

Brian Points 8766

L'une des principales causes est l'utilisation d'une boucle à retard qui est calibrée au démarrage du programme. Ils comptent le nombre de fois qu'une boucle s'exécute dans un laps de temps connu et le divisent pour générer des délais plus petits. Cela peut ensuite être utilisé pour mettre en œuvre une fonction sleep() afin de rythmer l'exécution du jeu. Les problèmes surviennent lorsque ce compteur est au maximum, car les processeurs sont tellement plus rapides dans la boucle que le petit délai finit par être beaucoup trop petit. De plus, les processeurs modernes changent de vitesse en fonction de la charge, parfois même sur une base par cœur, ce qui rend le délai encore plus faible.

Pour les très vieux jeux PC, ils couraient aussi vite qu'ils le pouvaient sans se soucier du rythme du jeu. C'était plutôt le cas à l'époque de l'IBM PC XT, où il existait un bouton turbo qui ralentissait le système pour correspondre à un processeur de 4,77 mhz.

Les jeux modernes et les bibliothèques comme DirectX ont accès à des minuteurs à haute précession et n'ont donc pas besoin d'utiliser des boucles de retard calibrées basées sur le code.

5voto

technomalogical Points 1859

Tous les premiers PC fonctionnaient à la même vitesse au début, il n'était donc pas nécessaire de tenir compte des différences de vitesse.

De plus, au début, de nombreux jeux avaient une charge de processeur assez fixe, il était donc peu probable que certaines images tournent plus vite que d'autres.

Aujourd'hui, avec vos enfants et vos jeux de tir FPS, vous pouvez regarder le sol une seconde, et le grand canyon la suivante, la variation de charge est plus fréquente :)

(Et, peu de consoles matérielles sont assez rapides pour faire tourner des jeux à 60 fps en permanence. Cela est principalement dû au fait que les développeurs de consoles optent pour le 30 Hz et rendent les pixels deux fois plus brillants...)

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X