ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [DirectX] QPC로 FPS(프레임 레이트) 구현하기
    C++/미분류 2024. 2. 20. 16:06

    QPC란 Query Performance Counter로, Windows에서 사용되는 시간 측정을 위한 기능입니다.

    이번 글에서는 QPC를 이용해서 함수의 호출 주기를 구하는 방법을 살펴보고, 프레임마다 호출되는 함수에 이 방법을 적용하여 FPS를 구해보도록 하겠습니다.

     

    이번 글은 이전 글의 코드([WinAPI] 로그 출력하기 (Console, TRACE))의 예제를 기반으로 작성되었습니다.

     


     

    1. 살펴보기

     

    WinAPI를 이용한 이전 프로젝트는 화면을 그리기 위해 OnRender함수를 매 프레임마다 호출합니다.

    따라서, 매 프레임마다 호출되는 OnRender함수의 호출 주기를 알 수 있다면, 프레임 레이트를 구할 수 있습니다.

     

    2. QPC 사용하기

     

    더보기

    QPC에서 주로 사용되는 함수는 다음과 같습니다.

    LARGE_INTEGER myPrevTime;
    LARGE_INTEGER myFrequency;
    
    QueryPerformanceFrequency(&myFrequency);
    QueryPerformanceCounter(&myPrevTime);

    QueryPerformanceFrequency는 카운터의 주기를 받아옵니다.

    주기란 1초에 해당하는 틱의 값으로, 시스템이 부팅될 때 고정되어 변하지 않습니다.

    QueryPerformanceCounter는 카운터의 현재 틱 값을 받아옵니다.

     

    호출 시점의 틱을 받아올 수 있다는 특징을 이용해서 다음과 같은 로직을 생각할 수 있습니다.

    • 프로그램 시작 시점의 Tick을 저장합니다. (A)
    • OnRender함수 호출 시의 Tick을 저장합니다. (B)
    • B-A를 계산하면, 프로그램 시작 시점과 OnRender 호출 시의 틱 차이를 얻을 수 있습니다.
    • A에 B를 대입합니다. (A = B)
    • 다음 OnRender함수 호출 시의 Tick을 저장합니다. (B`)
    • B`-A값은 이전 OnRender함수와 현재 OnRender함수 호출 사이의 틱 간격입니다.
    • (B`-A)/Frequency는 OnRender함수 호출 사이의 시간 간격입니다. (단위는 Second)

     

    위 로직을 구현하기 위해, 기존 코드의 클래스 필드에 다음과 같은 변수를 추가하겠습니다.

    class MyApp {
    ...
    private:
        ...
        LARGE_INTEGER myPrevTime;
        LARGE_INTEGER myFrequency;
        ...
    };

    myPrevTime은 이전 틱 값을 저장할 변수, myFrequency는 카운터의 주기를 저장할 변수입니다.

     

    해당 값을 프로그램 실행 시 초기화합니다.

    HRESULT MyApp::initialize(HINSTANCE hInstance) {
        QueryPerformanceFrequency(&myFrequency);
        QueryPerformanceCounter(&myPrevTime);
        ...
    }

    Initialize함수의 제일 처음 부분에 초기화 코드를 추가했습니다.

     

    다음은 OnRender 함수입니다.

    HRESULT MyApp::OnRender() {
        HRESULT hr = S_OK;
        hr = CreateDeviceResources();
        if (SUCCEEDED(hr)) {
            LARGE_INTEGER currentTime;
            QueryPerformanceCounter(&currentTime);
            FLOAT deltaTime = (FLOAT)(
                (DOUBLE)(currentTime.QuadPart - myPrevTime.QuadPart) / (DOUBLE)(myFrequency.QuadPart)
            );
            myPrevTime = currentTime;
            ...
    }

    OnRender함수가 정상적으로 실행되는 조건문 직후에 함수의 호출 주기를 구하는 코드를 추가했습니다.

    위 코드는 현재 틱(currentTime)을 구하고, 이전 호출의 틱(myPrevTime)의 차를 계산하는 코드입니다.

    위 코드를 통해 계산된 deltaTime이 OnRender함수 호출 사이의 시간이 됩니다.

     

    3. FPS 구하기

     

    더보기

    FPS는 1초 동안 지나간 프레임의 수 입니다.

    OnRender함수는 매 프레임마다 호출되며, OnRender함수의 호출 간격을 알 수 있으니, 이 시간을 역수로 취하면 1초 동안 지나간 프레임의 수를 구할 수 있습니다.

     

    하지만 매 간격마다 프레임 레이트를 갱신할 경우, 프리징이 걸렸을 때 등 프레임 레이트가 크게 변동하는 상황이 발생할 수 있습니다.

    민감한 프로그램이라면 위의 방법을 취하는 것이 맞지만, 일정 프레임 단위로 평균 프레임 레이트를 표시하는 것도 합리적인 방법이 될 수 있습니다.

     

    이번에는 10프레임의 평균에 대하여 프레임 레이트를 구해보도록 하겠습니다.

     

    우선, 프레임 레이트 계산에 필요한 변수를 클래스에 추가합니다.

    class MyApp {
    ...
    private:
        ...
        std::deque<FLOAT> frameTime;
        const int frameAvgCount = 10;
        ...
    };

    frameTime은 이전 10프레임의 시간을 저장할 컨테이너 입니다.

    10개 이상의 값이 들어올 경우 이전 값을 제외해야 하니 deque를 사용했습니다.

    frameAvgCount는 평균을 계산할 프레임의 수 입니다.

     

    OnRender함수의 deltaTime을 계산하는 코드 이후, 프레임의 평균을 계산하는 코드를 추가하겠습니다.

    HRESULT MyApp::OnRender() {
        HRESULT hr = S_OK;
        hr = CreateDeviceResources();
        if (SUCCEEDED(hr)) {
            ...
            frameTime.push_back(deltaTime);
            while (frameTime.size() > frameAvgCount) frameTime.pop_front();
    
            FLOAT totalTime = 0.0f;
            for (auto& i : frameTime) {
                totalTime += i;
            }
            FLOAT frameRate = 1 / (totalTime / frameAvgCount);
            std::cout << frameRate << "\n";
            ...
    }

    현재 프레임의 시간을 frameTime에 추가하고, 컨테이너의 원소 수가 frameAvgCount를 초과했을 경우 초과하지 않을 때 까지 비워줍니다.

    이후 컨테이너의 값의 평균을 역수로 취하면 10프레임 동안의 프레임 레이트가 계산됩니다.

     


     

    QPC의 사용법과, 이를 이용한 프레임 레이트를 구하는 방법을 알아보았습니다.

    이번 글에서 사용된 코드는 Github에서 보실 수 있습니다.

     

    GitHub - ruru14/WinapiStudy

    Contribute to ruru14/WinapiStudy development by creating an account on GitHub.

    github.com

    글과 예제에서는 콘솔 로그를 취해서 프레임 레이트를 출력했습니다.

    만드시는 프로그램에 따라, 프레임 레이트가 UI에 노출되어야 할 수 있으니, 값을 적절히 이용하는 것이 좋겠습니다.

     

    이번 글이 도움이 되셨기를 바랍니다.

    감사합니다.

    댓글

Designed by Tistory.