Mengapa beberapa game lama berjalan terlalu cepat pada perangkat keras modern?

64

Saya punya beberapa program lama. Saya menggunakan komputer Windows era 90-an dan mencoba menjalankannya di komputer yang relatif modern. Yang cukup menarik, mereka berlari dengan kecepatan yang sangat cepat - bukan, bukan jenis 60 frame per detik, melainkan jenis oh-my-god-the-character-is-walking-at-the-speed-of-sound cepat. Saya akan menekan tombol panah dan sprite karakter akan melintasi layar lebih cepat dari biasanya. Kemajuan waktu dalam permainan itu terjadi jauh lebih cepat dari yang seharusnya. Bahkan ada program yang dibuat untuk memperlambat CPU Anda sehingga game-game ini sebenarnya dapat dimainkan.

Saya pernah mendengar bahwa ini terkait dengan permainan tergantung pada siklus CPU, atau sesuatu seperti itu. Pertanyaan saya adalah:

  • Mengapa game lama melakukan ini, dan bagaimana mereka bisa lolos?
  • Bagaimana permainan yang lebih baru tidak melakukan ini dan berjalan secara independen dari frekuensi CPU?
TreyK
sumber
Ini beberapa waktu yang lalu dan saya tidak ingat melakukan tipu daya kompatibilitas, tapi itu intinya. Ada banyak informasi di luar sana tentang cara memperbaikinya, tetapi tidak begitu banyak tentang mengapa mereka menjalankannya, itulah yang saya tanyakan.
TreyK
9
Ingat tombol turbo di PC lama? : D
Viktor Mellgren
1
Ah. Buat saya ingat penundaan 1 detik pada ABC80 (PC berbasis Swedia z80 dengan Basic). FOR F IN 0 TO 1000; NEXT F;
Macke
1
Hanya untuk memperjelas, "beberapa program lama saya menggunakan komputer Windows era 90-an awal" adalah program-program DOS pada mesin Windows, atau program Windows di mana perilaku ini terjadi? Saya sudah terbiasa melihatnya di DOS, tapi bukan windows, IIRC.
Orang Brasil itu
Lihat juga CPU atau framerate yang membatasi permainan yang lebih lama
BlueRaja - Danny Pflughoeft

Jawaban:

52

Saya percaya mereka mengira jam sistem akan berjalan pada kecepatan tertentu, dan terikat pada timer internal mereka ke laju jam itu. Sebagian besar gim ini mungkin berjalan pada DOS, dan merupakan mode nyata (dengan akses perangkat keras langsung dan lengkap) dan mengasumsikan Anda menjalankan sistem iirc 4,77 MHz untuk PC dan prosesor standar apa pun yang dijalankan model untuk sistem lain seperti Amiga.

Mereka juga mengambil jalan pintas yang cerdas berdasarkan asumsi-asumsi itu termasuk menghemat sedikit sumber daya dengan tidak menulis loop waktu internal di dalam program. Mereka juga mengambil daya prosesor sebanyak yang mereka bisa - yang merupakan ide yang baik di masa-masa chip yang lambat dan sering kali didinginkan secara pasif!

Awalnya satu cara untuk mengatasi kecepatan prosesor yang berbeda adalah tombol Turbo tua yang baik (yang memperlambat sistem Anda). Aplikasi modern dalam mode terproteksi dan OS cenderung mengelola sumber daya - mereka tidak akan mengizinkan aplikasi DOS (yang berjalan di NTVDM pada sistem 32-bit) menggunakan semua prosesor dalam banyak kasus. Singkatnya, OS menjadi lebih pintar, seperti halnya API.

Sangat berdasarkan dari panduan ini pada Oldskool PC di mana logika dan memori mengecewakan saya - ini adalah bacaan yang bagus, dan mungkin masuk lebih dalam ke "mengapa".

Hal-hal seperti CPUkiller menggunakan sumber daya sebanyak mungkin untuk "memperlambat" sistem Anda, yang tidak efisien. Anda akan lebih baik menggunakan DOSBox untuk mengatur kecepatan jam yang dilihat aplikasi Anda.

Journeyman Geek
sumber
14
Beberapa dari game ini bahkan tidak mengasumsikan apa pun, mereka berlari secepat yang mereka bisa, yang 'dapat dimainkan' pada CPU itu ;-)
Jan Doggen
2
Untuk informasi kembali. "Bagaimana game yang lebih baru tidak melakukan ini dan berjalan secara independen dari frekuensi CPU?" coba cari gamedev.stackexchange.com untuk sesuatu seperti game loop. Pada dasarnya ada 2 metode. 1) Jalankan secepat mungkin dan skala kecepatan gerakan dll berdasarkan seberapa cepat game berjalan. 2) Jika Anda menunggu terlalu cepat ( sleep()) sampai kami siap untuk 'centang' berikutnya.
George Duckett
24

Sebagai tambahan untuk jawaban Journeyman Geek (karena hasil edit saya ditolak) untuk orang-orang yang tertarik pada bagian pengkodean / perspektif pengembang:

Dari perspektif programmer, bagi mereka yang tertarik, waktu DOS adalah waktu di mana setiap centang CPU sangat penting sehingga programmer menyimpan kode secepat mungkin.

Skenario khas di mana program mana pun akan berjalan pada kecepatan CPU maks adalah ini sederhana (pseudo C):

int main()
{
    while(true)
    {

    }
}

ini akan berjalan selamanya, sekarang, mari kita ubah snipet kode ini menjadi pseudo-DOS-game:

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

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

kecuali DrawGameOnScreenfungsi menggunakan buffering ganda / V-sync (yang agak mahal di hari-hari ketika game DOS dibuat), game akan berjalan pada kecepatan CPU maksimum. Pada ponsel modern i7 ini akan berjalan sekitar 1.000.000 hingga 5.000.000 kali per detik (tergantung pada konfigurasi laptop dan penggunaan cpu saat ini).

Ini berarti bahwa jika saya bisa membuat game DOS bekerja pada CPU modern saya di windows 64bit saya, saya bisa mendapatkan lebih dari seribu (1000!) FPS yang terlalu cepat untuk dimainkan manusia jika proses fisika "mengasumsikan" itu berjalan antara 50-60 fps.

Apa yang dilakukan pengembang saat ini adalah:

  1. Aktifkan V-Sync di dalam game (* tidak tersedia untuk aplikasi berjendela ** [alias hanya tersedia di aplikasi layar penuh])
  2. Ukur perbedaan waktu antara pembaruan terakhir dan perbarui fisika sesuai dengan perbedaan waktu yang secara efektif membuat game / program berjalan pada kecepatan yang sama terlepas dari tingkat FPS
  3. Batasi framerate secara terprogram

*** tergantung pada konfigurasi kartu grafis / driver / os itu mungkin menjadi mungkin.

Untuk poin 1 tidak ada contoh yang akan saya tunjukkan karena sebenarnya tidak ada "pemrograman". Hanya menggunakan fitur grafis.

Sedangkan untuk poin 2 dan 3 saya akan menunjukkan cuplikan dan penjelasan kode yang sesuai:

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;
        }
    }
}

Di sini Anda dapat melihat input pengguna dan fisika memperhitungkan perbedaan waktu, namun Anda masih bisa mendapatkan 1000+ FPS di layar karena loop berjalan secepat mungkin. Karena mesin fisika tahu berapa lama waktu berlalu, itu tidak harus bergantung pada "tidak ada asumsi" atau "framerate tertentu" sehingga permainan akan bekerja pada kecepatan yang sama pada cpu apa pun.

3:

Apa yang dapat dilakukan pengembang untuk membatasi framerate, misalnya, 30 FPS sebenarnya tidak lebih sulit, lihat saja:

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;
        }
    }
}

Apa yang terjadi di sini adalah bahwa program menghitung berapa milidetik yang telah berlalu, jika jumlah tertentu tercapai (33 ms) maka ia menggambar ulang layar permainan, secara efektif menerapkan frame rate mendekati ~ 30.

Juga, tergantung pada pengembangnya, ia dapat memilih untuk membatasi SEMUA pemrosesan hingga 30 fps dengan kode di atas sedikit dimodifikasi untuk ini:

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;
            }
        }
    }
}

Ada beberapa metode lain, dan beberapa di antaranya benar-benar saya benci.

Misalnya, menggunakan sleep(<amount of milliseconds>).

Saya tahu ini adalah salah satu metode untuk membatasi framerate, tetapi apa yang terjadi ketika pemrosesan gim Anda membutuhkan 3 milidetik atau lebih? Dan kemudian Anda menjalankan tidur ...

ini akan menghasilkan framerate yang lebih rendah daripada yang sleep()seharusnya hanya menyebabkan.

Sebagai contoh, mari kita ambil waktu tidur 16 ms. ini akan membuat program berjalan pada 60 hz. sekarang pemrosesan data, input, gambar dan semua hal membutuhkan waktu 5 milidetik. kami berada di 21 milidetik untuk satu putaran sekarang yang menghasilkan sedikit kurang dari 50 hz, sementara Anda dapat dengan mudah masih berada di 60 hz tetapi karena tidur tidak mungkin.

Salah satu solusinya adalah membuat tidur adaptif dalam bentuk mengukur waktu pemrosesan dan mengurangi waktu pemrosesan dari tidur yang diinginkan sehingga memperbaiki "bug" kami:

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);
        }
    }
}
Alat
sumber
16

Salah satu penyebab utama adalah menggunakan loop penundaan yang dikalibrasi saat program dimulai. Mereka menghitung berapa kali loop dieksekusi dalam jumlah waktu yang diketahui dan membaginya untuk menghasilkan penundaan yang lebih kecil. Ini kemudian dapat digunakan untuk mengimplementasikan fungsi sleep () untuk mempercepat eksekusi game. Masalah muncul ketika penghitung ini dimaksimalkan karena prosesor menjadi jauh lebih cepat pada loop sehingga penundaan kecil menjadi terlalu kecil. Selain itu prosesor modern mengubah kecepatan berdasarkan beban, kadang-kadang bahkan berdasarkan per-inti, yang membuat penundaan lebih lama lagi.

Untuk gim-gim PC yang benar-benar tua, mereka hanya berlari secepat mungkin tanpa mempedulikan kecepatan permainan. Ini lebih banyak terjadi pada IBM PC XT hari namun di mana ada tombol turbo yang memperlambat sistem untuk mencocokkan prosesor 4,77mhz karena alasan ini.

Gim dan perpustakaan modern seperti DirectX memiliki akses ke penghitung waktu tinggi sehingga tidak perlu menggunakan loop penundaan berbasis kode yang dikalibrasi.

Brian
sumber
4

Semua PC pertama berjalan pada kecepatan yang sama di awal, jadi tidak perlu memperhitungkan perbedaan dalam kecepatan.

Juga, banyak gim pada awalnya memiliki cpu-load yang cukup tetap, jadi tidak mungkin beberapa frame akan berjalan lebih cepat daripada yang lain.

Saat ini, dengan anak-anak Anda dan penembak FPS mewah Anda, Anda dapat melihat lantai satu detik, dan di grand canyon berikutnya, variasi beban terjadi lebih sering. :)

(Dan, beberapa konsol perangkat keras cukup cepat untuk menjalankan game pada 60 fps terus-menerus. Hal ini sebagian besar disebabkan oleh kenyataan bahwa pengembang konsol memilih 30 Hz dan membuat piksel dua kali lebih mengkilap ...)

Macke
sumber