왜 오래된 게임이 현대 하드웨어에서 너무 빨리 실행됩니까?


64

나는 90 년대 초반의 Windows 컴퓨터를 꺼내고 비교적 현대적인 컴퓨터에서 실행하려고 시도한 몇 가지 오래된 프로그램을 가지고 있습니다. 흥미롭게도 그들은 초당 60 프레임의 빠른 속도가 아니라 오히려 굉장히 빠른 속도로 달렸습니다. 빠른. 화살표 키를 누르면 캐릭터의 스프라이트가 화면에서 일반보다 훨씬 빠르게 압축됩니다. 게임의 시간 진행이 예상보다 훨씬 빠르게 진행되고있었습니다. CPU느리게 만들어 프로그램 이 실제로 재생되도록하는 프로그램도 있습니다 .

나는 이것이 CPU 사이클 또는 이와 유사한 것에 따라 게임과 관련이 있다고 들었습니다. 내 질문은 :

  • 오래된 게임이 왜 그렇게합니까?
  • 최신 게임에서 어떻게하지 않고 CPU 주파수와 독립적으로 실행합니까?

이것은 얼마 전이었고 호환성 트릭을 수행하는 것을 기억하지 못하지만 그게 요점입니다. 이 문제를 해결하는 방법에 대한 많은 정보가 있지만 정확히 그런 식으로 실행 하는지대해서는 별로 묻지 않습니다.
TreyK

9
구형 PC의 터보 버튼을 기억하십니까? : D
빅토르 Mellgren

1
아 ABC80 (Swedish z80 기반 PC 기본)의 1 초 지연을 기억합니다. FOR F IN 0 TO 1000; NEXT F;
Macke

1
"90 년대 초반의 Windows 컴퓨터에서 가져온 몇 가지 오래된 프로그램"은 Windows 시스템의 DOS 프로그램 또는이 동작이 발생하는 Windows 프로그램입니까? 나는 DOS에서 그것을 보는 데 익숙하지만 Windows, IIRC는 아닙니다.
그 브라질 사람

답변:


52

그들은 시스템 클럭이 특정 속도로 작동한다고 가정하고 내부 타이머를 해당 클럭 속도에 연결했습니다. 이러한 게임의 대부분은 아마도 DOS에서 실행되었으며 실제 모드 (완전한 직접 하드웨어 액세스)와 PC 용 iirc 4.77MHz 시스템 및 Amiga와 같은 다른 시스템에서 모델이 실행되는 표준 프로세서를 실행한다고 가정했습니다 .

또한 프로그램 내부에 내부 타이밍 루프를 작성하지 않음으로써 약간의 리소스를 절약하는 것을 포함하여 이러한 가정을 기반으로 영리한 지름길을 채택했습니다. 또한 가능한 한 많은 프로세서 전력을 소비했습니다. 이는 느리고 수동적으로 냉각되는 칩 시대에 적절한 아이디어였습니다!

처음에는 다른 프로세서 속도 를 피할 수 있는 좋은 방법 중 하나는 이전의 터보 버튼 (시스템 속도를 늦추는 것)이었습니다. 현대 응용 프로그램은 보호 모드에 있고 OS는 자원을 관리하는 경향이있다 - 그들이하지 않을 수 있습니다 (어쨌든 32 비트 시스템에서 NTVDM에서 실행되는)는 DOS 응용 프로그램이 많은 경우에 프로세서 모두를 사용 할 수 있습니다. 요컨대, OS와 마찬가지로 API도 더 똑똑해졌습니다.

로직과 메모리가 실패한 Oldskool PC의이 가이드에 크게 기반을두고 있습니다 .

CPUkiller 와 같은 기능 은 시스템을 "느리게"하기 위해 가능한 한 많은 리소스를 사용하므로 비효율적입니다. DOSBox 를 사용하여 응용 프로그램의 클럭 속도를 관리하는 것이 좋습니다 .


14
이 게임 중 일부는 아무것도 가정하지 않았으며 가능한 한 빨리 실행되었으며 CPU에서 '재생 가능'했습니다.
Jan Doggen

2
다시 정보를 위해. "최신 게임은 어떻게이 작업을 수행하지 않고 CPU 주파수와 독립적으로 실행합니까?" gamedev.stackexchange.com과 같은 것을 검색하십시오 game loop. 기본적으로 2 가지 방법이 있습니다. 1) 가능한 빨리 실행하고 게임 실행 속도에 따라 이동 속도 등을 조정하십시오. 2) 너무 빠르면 sleep()다음 '틱'에 대한 준비가 될 때까지 기다리십시오 ( ).
George Duckett

24

코딩 파트 / 개발자 관점에 관심이있는 사람들에 대한 Journeyman Geek의 답변 (내 편집이 거부 되었기 때문에)에 대한 추가로 :

프로그래머의 관점에서, 관심이있는 사람들을 위해 DOS 시간은 모든 CPU 틱이 중요한 시간이되었으므로 프로그래머는 코드를 최대한 빨리 유지했습니다.

프로그램이 최대 CPU 속도로 실행되는 일반적인 시나리오는 다음과 같습니다 (의사 C).

int main()
{
    while(true)
    {

    }
}

이것은 영원히 실행될 것입니다.이 코드 스 니펫을 의사 DOS 게임으로 바꾸어 봅시다.

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

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

DrawGameOnScreen함수가 더블 버퍼링 / V- 싱크를 사용 하지 않는 한 (DOS 게임이 만들어진 날에는 비용이 많이 들었습니다), 게임은 최대 CPU 속도로 실행됩니다. 현대의 모바일 i7에서는 랩톱 구성 및 현재 CPU 사용량에 따라 초당 약 1,000,000 ~ 5,000,000 회 실행됩니다.

즉, 64 비트 창에서 최신 CPU로 작동하는 DOS 게임을 얻을 수 있다면 물리 처리가 "가정"한다고 가정하면 사람이 재생하기에는 너무 빠른 1000 (1000!) 이상의 FPS를 얻을 수 있습니다. 50-60fps.

현재 개발자가 할 수있는 일은 다음과 같습니다.

  1. 게임에서 V-Sync 사용 (* 윈도우 응용 프로그램에는 사용할 수 없음 ** (일명 전체 화면 응용 프로그램에서만 사용 가능))
  2. FPS 속도에 관계없이 게임 / 프로그램을 동일한 속도로 효과적으로 실행하는 시간 차이에 따라 마지막 업데이트 간의 시간 차이를 측정하고 물리를 업데이트하십시오.
  3. 프로그래밍 방식으로 프레임 속도 제한

*** 그래픽 카드 / 드라이버 / OS 구성에 따라 가능할 수 있습니다.

포인트 1의 경우 실제로 "프로그래밍"이 아니기 때문에 표시 할 예가 없습니다. 그래픽 기능 만 사용하고 있습니다.

포인트 2와 3에 대해서는 해당 코드 스 니펫과 설명을 보여줍니다.

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

여기에서 사용자 입력과 물리가 시간 차이를 고려한 것을 볼 수 있지만 루프가 가능한 빨리 실행되기 때문에 여전히 화면에 1000+ FPS를 얻을 수 있습니다. 물리 엔진은 시간이 얼마나 걸 렸는지 알기 때문에 "가정 없음"또는 "특정 프레임 속도"에 의존 할 필요가 없으므로 모든 CPU에서 게임이 동일한 속도로 작동합니다.

삼:

예를 들어 30 FPS로 프레임 속도를 제한하기 위해 개발자가 할 수있는 작업은 실제로 어렵지 않습니다.

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

여기서 발생하는 것은 프로그램이 몇 밀리 초가 경과했는지 계산하고 특정 양에 도달하면 (33ms) 게임 화면을 다시 그리며 ~ 30 근처의 프레임 속도를 효과적으로 적용합니다.

또한 개발자에 따라 위 코드를 약간 수정하여 모든 처리를 30fps로 제한하도록 선택할 수 있습니다.

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

몇 가지 다른 방법이 있으며 그중 일부는 정말 싫어합니다.

예를 들어을 사용 sleep(<amount of milliseconds>)합니다.

이것이 프레임 속도를 제한하는 한 가지 방법이라는 것을 알고 있지만 게임 처리에 3 밀리 초 이상이 걸리면 어떻게됩니까? 그리고 당신은 잠을 실행합니다 ...

이로 인해 발생하는 프레임 속도보다 프레임 속도가 느려집니다 sleep().

예를 들어 16ms의 절전 시간을 보자. 이렇게하면 프로그램이 60Hz에서 실행됩니다. 이제 데이터, 입력, 그리기 및 모든 자료를 처리하는 데 5 밀리 초가 걸립니다. 우리는 하나의 루프에 대해 21 밀리 초에 있습니다. 이는 50 hz보다 약간 작습니다. 그러나 여전히 60 hz에있을 수 있지만 수면 때문에 불가능합니다.

한 가지 해결책은 처리 시간을 측정하고 원하는 수면에서 처리 시간을 차감하여 "버그"를 수정하는 형태로 적응 형 절전 모드 를 만드는 것입니다 .

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

16

주요 원인 중 하나는 프로그램 시작시 교정되는 지연 루프를 사용하는 것입니다. 알려진 시간 동안 루프가 몇 번 실행되는지 세고 더 작은 지연을 생성하기 위해 나눕니다. 그런 다음 게임 실행 속도를 높이기 위해 sleep () 함수를 구현하는 데 사용할 수 있습니다. 루프에서 프로세서가 너무 빨라서 작은 지연이 너무 작아서이 카운터가 최대가되면 문제가 발생합니다. 또한 최신 프로세서는로드에 따라, 때로는 코어 단위로도 속도를 변경하므로 지연이 훨씬 더 줄어 듭니다.

정말 오래된 PC 게임의 경우 게임 속도를 높이려는 시도와 상관없이 최대한 빨리 실행했습니다. 그러나 IBM PC XT 시절에는 터보 버튼이 존재하여 시스템이 4.77mhz 프로세서와 일치하도록 속도를 늦춘 경우가 더 많았습니다.

DirectX와 같은 최신 게임 및 라이브러리는 높은 세차 타이머에 액세스 할 수 있으므로 보정 된 코드 기반 지연 루프를 사용할 필요가 없습니다.


4

최초의 모든 PC는 처음에 동일한 속도로 실행되었으므로 속도 차이를 고려할 필요가 없었습니다.

또한 처음에는 많은 게임이 꽤 고정 된 cpu-load를 가지고 있었기 때문에 일부 프레임이 다른 프레임보다 더 빨리 실행되지는 않았습니다.

요즘에는 아이들과 함께 멋진 FPS 슈팅 게임을 사용하면 1 초 동안 바닥을 볼 수 있고 다음에 그랜드 캐년에서는 하중 변동이 더 자주 발생합니다. :)

(그리고 60fps로 게임을 지속적으로 실행할 수있을 정도로 빠른 하드웨어 콘솔은 거의 없습니다. 이것은 대부분 콘솔 개발자가 30Hz를 선택하고 픽셀을 두 배로 빛나게하기 때문입니다 ...)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.