Warum laufen manche alten Spiele auf moderner Hardware viel zu schnell?

64

Ich habe ein paar alte Programme, die ich auf einem Windows-Computer aus den frühen 90er Jahren erstellt und versucht habe, sie auf einem relativ modernen Computer auszuführen. Interessanterweise liefen sie mit einer blitzschnellen Geschwindigkeit - nein, nicht die 60 Bilder pro Sekunde, sondern die Art von "Oh mein Gott, der Charakter geht mit der Geschwindigkeit des Klangs" schnell. Ich drückte eine Pfeiltaste und das Sprite des Charakters bewegte sich viel schneller als normal über den Bildschirm. Der Zeitverlauf im Spiel verlief viel schneller als es sollte. Es gibt sogar Programme, die Ihre CPU verlangsamen, damit diese Spiele tatsächlich spielbar sind.

Ich habe gehört, dass dies abhängig von den CPU-Zyklen mit dem Spiel zusammenhängt oder so ähnlich. Meine Fragen sind:

  • Warum machen ältere Spiele das und wie sind sie damit durchgekommen?
  • Wie machen neuere Spiele das nicht und laufen unabhängig von der CPU-Frequenz?
TreyK
quelle
Das ist eine Weile her und ich kann mich nicht erinnern, irgendwelche Kompatibilitäts-Tricks gemacht zu haben, aber das ist nebensächlich. Es gibt viele Informationen darüber, wie man das behebt, aber nicht so sehr darüber, warum sie genau so laufen, was ich frage.
TreyK
9
Erinnern Sie sich an die Turbo-Taste bei älteren PCs? : D
Viktor Mellgren
1
Ah. Erinnert mich an die Verzögerung von 1 Sekunde beim ABC80 (schwedischer z80-basierter PC mit Basic). FOR F IN 0 TO 1000; NEXT F;
Macke
1
Nur um zu verdeutlichen: "Ein paar alte Programme, die ich aus einem Windows-Computer aus den frühen 90er Jahren gezogen habe", sind diese DOS-Programme auf einem Windows-Computer oder Windows-Programme, bei denen dieses Verhalten auftritt? Ich bin es gewohnt, es unter DOS zu sehen, aber nicht unter Windows, IIRC.
Der Brasilianer
Siehe auch CPU- oder
Bildratenbegrenzung

Antworten:

52

Ich glaube, sie gingen davon aus, dass die Systemuhr mit einer bestimmten Rate laufen würde, und banden ihre internen Timer an diese Taktrate an. Die meisten dieser Spiele liefen wahrscheinlich unter DOS und waren im Real-Modus (mit vollständigem, direktem Hardware-Zugriff) und gingen davon aus, dass Sie ein iirc 4,77 MHz-System für PCs und einen beliebigen Standardprozessor verwenden, den dieses Modell für andere Systeme wie den Amiga ausführte .

Sie nahmen auch clevere Verknüpfungen, die auf diesen Annahmen basierten, einschließlich der Einsparung eines winzigen Teils der Ressourcen, indem sie keine internen Timing-Schleifen innerhalb des Programms schrieben. Sie nahmen auch so viel Prozessorleistung auf, wie sie konnten - was zu Zeiten langsamer, oft passiv gekühlter Chips eine anständige Idee war!

Anfänglich war ein Weg, um die unterschiedliche Prozessorgeschwindigkeit zu umgehen, der gute alte Turbo-Knopf (der Ihr System verlangsamte). Moderne Anwendungen sind im geschützten Modus und das Betriebssystem dazu tendiert Ressourcen zu verwalten - sie würde nicht erlauben , eine DOS - Anwendung (die auf einem 32-Bit - System ohnehin in NTVDM ausgeführt wird ) , die alle den Prozessor in vielen Fällen aufbrauchen. Kurz gesagt, Betriebssysteme und APIs sind schlauer geworden.

Dieses Handbuch basiert stark auf Oldskool-PCs, bei denen Logik und Speicher versagten. Es ist eine großartige Lektüre und geht wahrscheinlich eingehender auf das "Warum" ein.

Sachen wie CPUkiller verbrauchen so viele Ressourcen wie möglich, um Ihr System zu "verlangsamen", was ineffizient ist. Sie sollten DOSBox verwenden , um die für Ihre Anwendung festgelegte Taktrate zu verwalten.

Geselle Geek
quelle
14
Einige dieser Spiele gingen nicht einmal von irgendetwas aus, sie liefen so schnell sie konnten, was auf diesen CPUs 'spielbar' war
;-)
2
Zur Information bezüglich. "Wie machen neuere Spiele das nicht und laufen unabhängig von der CPU-Frequenz?" Versuchen Sie, gamedev.stackexchange.com nach etwas wie zu durchsuchen game loop. Grundsätzlich gibt es 2 Methoden. 1) Laufen Sie so schnell wie möglich und skalieren Sie die Bewegungsgeschwindigkeiten usw. basierend darauf, wie schnell das Spiel läuft. 2) Wenn Sie zu schnell sind, warten Sie ( sleep()), bis wir für das nächste 'Häkchen' bereit sind.
George Duckett
24

Als Ergänzung zu Journeyman Geeks Antwort (weil meine Bearbeitung abgelehnt wurde) für die Leute, die sich für den Codierungsteil / die Entwicklerperspektive interessieren:

Aus Sicht der Programmierer waren die DOS-Zeiten für diejenigen, die interessiert sind, Zeiten, in denen jeder CPU-Tick wichtig war, damit die Programmierer den Code so schnell wie möglich hielten.

Ein typisches Szenario, in dem ein Programm mit maximaler CPU-Geschwindigkeit ausgeführt wird, ist das folgende einfache Szenario (Pseudo-C):

int main()
{
    while(true)
    {

    }
}

das wird für immer laufen, jetzt wollen wir dieses Code-Snippet in ein Pseudo-DOS-Spiel verwandeln:

int main()
{
    bool GameRunning = true;
    while(GameRunning)
    {
        ProcessUserMouseAndKeyboardInput();
        ProcessGamePhysics();
        DrawGameOnScreen();

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

Sofern die DrawGameOnScreenFunktionen nicht Double Buffering / V-Sync verwenden (was in den Tagen, in denen DOS-Spiele erstellt wurden, ziemlich teuer war), wird das Spiel mit maximaler CPU-Geschwindigkeit ausgeführt. Auf einem modernen mobilen i7 würde dies mit einer Geschwindigkeit von 1.000.000 bis 5.000.000 Mal pro Sekunde (abhängig von der Laptop-Konfiguration und der aktuellen CPU-Auslastung) ausgeführt.

Dies würde bedeuten, dass, wenn ich ein DOS-Spiel auf meiner modernen CPU in meinen 64-Bit-Fenstern zum Laufen bringen könnte, ich mehr als tausend (1000!) FPS bekommen könnte, was für jeden Menschen zu schnell ist, wenn die physikalische Verarbeitung davon ausgeht, dass es läuft zwischen 50-60 fps.

Was Entwickler heute tun können, ist:

  1. Aktiviere V-Sync im Spiel (* nicht verfügbar für fensterbasierte Anwendungen ** [auch bekannt als nur in Vollbild-Apps])
  2. Messen Sie die Zeitdifferenz zwischen der letzten Aktualisierung und der Aktualisierung der Physik anhand der Zeitdifferenz, die bewirkt, dass das Spiel / Programm unabhängig von der FPS-Rate mit derselben Geschwindigkeit ausgeführt wird
  3. Begrenzen Sie die Framerate programmgesteuert

*** Abhängig von der Konfiguration der Grafikkarte / des Treibers / des Betriebssystems ist dies möglicherweise möglich.

Für Punkt 1 gibt es kein Beispiel, das ich zeigen werde, weil es eigentlich keine "Programmierung" ist. Es werden nur die Grafikfunktionen verwendet.

Zu Punkt 2 und 3 zeige ich die entsprechenden Codefragmente und Erläuterungen:

2:

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

        //process movement based on how many time passed and which keys are 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;
        }
    }
}

Hier können Sie sehen, dass die Benutzereingaben und die Physik den Zeitunterschied berücksichtigen. Sie können jedoch immer noch über 1000 FPS auf dem Bildschirm anzeigen, da die Schleife so schnell wie möglich ausgeführt wird. Da die Physik-Engine weiß, wie viel Zeit vergangen ist, muss sie nicht auf "keine Annahmen" oder "eine bestimmte Framerate" angewiesen sein, damit das Spiel auf jeder CPU mit der gleichen Geschwindigkeit funktioniert.

3:

Was Entwickler tun können, um die Framerate auf beispielsweise 30 FPS zu begrenzen, ist eigentlich nichts Schwierigeres.

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

    double FPS_WE_WANT = 30;
    //how many milliseconds need to pass before we need to draw again so we get the framerate we want?
    double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
    //For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
    double LastDraw = GetCurrentTime();

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

        //process movement based on how many time passed and which keys are pressed
        ProcessUserMouseAndKeyboardInput(TimeDifference);

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

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

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

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

Was hier passiert, ist, dass das Programm zählt, wie viele Millisekunden vergangen sind. Wenn eine bestimmte Zeitspanne (33 ms) erreicht ist, wird der Spielbildschirm neu gezeichnet und eine Bildrate von ungefähr 30 angewendet.

Abhängig vom Entwickler kann er die ALL-Verarbeitung auf 30 fps beschränken, wobei der obige Code geringfügig geändert wird:

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

    double FPS_WE_WANT = 30;
    //how many miliseconds need to pass before we need to draw again so we get the framerate we want?
    double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
    //For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
    double LastDraw = GetCurrentTime();

    while(GameRunning)
    {

        LastTick = GetCurrentTime();
        TimeDifference = LastTick-LastDraw;

        //if certain amount of miliseconds pass...
        if(TimeDifference >= TimeToPassBeforeNextDraw)
        {
            //process movement based on how many time passed and which keys are 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 drawn the game
            LastDraw = LastTick;

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

Es gibt ein paar andere Methoden, und einige davon hasse ich wirklich.

Zum Beispiel mit sleep(<amount of milliseconds>).

Ich weiß, dass dies eine Methode ist, um die Framerate zu begrenzen, aber was passiert, wenn Ihre Spielverarbeitung 3 Millisekunden oder mehr dauert? Und dann führen Sie den Schlaf aus ...

Dies führt zu einer niedrigeren Bildrate als diejenige, die nur sleep()verursacht werden sollte.

Nehmen wir zum Beispiel eine Ruhezeit von 16 ms. Dies würde das Programm auf 60 Hz laufen lassen. Jetzt dauert die Verarbeitung der Daten, der Eingaben, des Zeichnens und des gesamten Materials 5 Millisekunden. Wir sind jetzt bei 21 Millisekunden für eine Schleife, was etwas weniger als 50 Hz ergibt, während Sie leicht noch bei 60 Hz sein könnten, aber wegen des Schlafes ist es unmöglich.

Eine Lösung wäre, einen adaptiven Schlaf in Form der Messung der Verarbeitungszeit und Abzug der Verarbeitungszeit von dem gewünschten Schlaf zu machen, was zur Behebung unseres "Fehlers" führt:

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

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

        //process movement based on how many time passed and which keys are 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);
        }
    }
}
Gizmo
quelle
16

Eine Hauptursache ist die Verwendung einer Verzögerungsschleife, die beim Programmstart kalibriert wird. Sie zählen, wie oft eine Schleife in einer bekannten Zeit ausgeführt wird, und teilen sie, um kleinere Verzögerungen zu erzeugen. Dies kann dann verwendet werden, um eine sleep () - Funktion zu implementieren, um die Ausführung des Spiels zu beschleunigen. Die Probleme treten auf, wenn dieser Zähler voll ist, weil die Prozessoren in der Schleife so viel schneller sind, dass die kleine Verzögerung viel zu klein wird. Außerdem ändern moderne Prozessoren die Geschwindigkeit abhängig von der Last, manchmal sogar pro Kern, wodurch die Verzögerung noch größer wird.

Für wirklich alte PC-Spiele liefen sie so schnell sie konnten, ohne zu versuchen, das Spiel zu beschleunigen. Dies war in den Tagen von IBM PC XT jedoch eher der Fall, als eine Turbotaste existierte, die das System aus diesem Grund verlangsamte, um mit einem 4,77 MHz-Prozessor übereinzustimmen.

Moderne Spiele und Bibliotheken wie DirectX haben Zugriff auf Zeitgeber mit hoher Präzision, sodass keine kalibrierten, auf Code basierenden Verzögerungsschleifen verwendet werden müssen.

Brian
quelle
4

Alle ersten PCs liefen zu Beginn mit der gleichen Geschwindigkeit, sodass Geschwindigkeitsunterschiede nicht berücksichtigt werden mussten.

Außerdem hatten viele Spiele zu Beginn eine ziemlich feste CPU-Auslastung, so dass es unwahrscheinlich war, dass einige Frames schneller als andere liefen.

Heutzutage können Sie mit Ihren Kindern und Ihren FPS-Schützen in einer Sekunde auf den Boden schauen und in der nächsten auf den Grand Canyon. Lastschwankungen treten häufiger auf. :)

(Und nur wenige Hardware-Konsolen sind schnell genug, um Spiele konstant mit 60 fps auszuführen. Dies liegt hauptsächlich daran, dass Konsolenentwickler 30 Hz wählen und die Pixel doppelt so glänzend machen ...)

Macke
quelle