120Hz·144Hz 가변 주사율 환경에서 입력 지연(Input Latency)을 극소화하는 프레임 루프 설계법

120Hz·144Hz 고주사율 및 VRR(G-Sync/FreeSync) 환경에서 발생하는 입력 지연의 구조적 원인을 분석하고 Late Polling과 프레임 페이싱을 결합한 최적의 게임 클라이언트 루프 설계 기법을 상세히 정리합니다.

✓ 복사 완료!
챕터게임플레이 로직
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
120Hz·144Hz 가변 주사율 환경에서 입력 지연(Input Latency)을 극소화하는 프레임 루프 설계법

핵심 요약

고주사율(120Hz/144Hz) 및 가변 주사율(VRR) 환경에서 입력 반응성을 극대화하려면 입력 수집(Input Polling) 시점을 프레임 시작부가 아닌 렌더 제출 직전으로 늦추는 Late Polling, GPU 큐 적체를 방지하는 프레임 페이싱(NVIDIA Reflex / Low Latency API) 그리고 고정 물리 시뮬레이션과 렌더 프레임을 분리하는 서브틱(Sub-tick) 보간 루프를 구축해야 합니다.

입력 지연의 실제 감소량은 엔진, 장치, GPU 부하, 디스플레이 설정에 따라 달라집니다. 특정 감소율을 보장하지 말고 프레임·입력·GPU 타임스탬프를 같은 장비와 장면에서 측정해야 합니다.


고주사율(120Hz/144Hz)과 VRR 환경에서 입력 지연은 왜 발생하는가?

디스플레이 주사율이 60Hz(16.67 ms16.67\text{ ms})에서 120Hz(8.33 ms8.33\text{ ms}), 144Hz(6.94 ms6.94\text{ ms})로 높아질수록 화면 갱신 주기는 급격히 줄어듭니다.
이론상 디스플레이 갱신 주기가 짧아진 만큼 사용자의 조작이 화면에 더 빠르게 반영되어야 하지만 실제 클라이언트 프레임 루프가 고주사율 파이프라인에 최적화되어 있지 않다면 체감 지연(Input-to-Photon Latency)은 기대만큼 줄어들지 않습니다.

입력 신호가 모니터 픽셀의 변화로 이어지기까지의 전체 지연 시간 TlatencyT_{\text{latency}}는 다음과 같은 파이프라인 단계의 합으로 정의됩니다.

Tlatency=Tpoll_to_sim+Tsim+Trender_queue+Tgpu+TscanoutT_{\text{latency}} = T_{\text{poll\_to\_sim}} + T_{\text{sim}} + T_{\text{render\_queue}} + T_{\text{gpu}} + T_{\text{scanout}}

일반적인 게임 엔진의 기본 루프에서는 프레임의 가장 첫 단계에서 OS 메시지 큐를 비우며 입력을 수집(Early Polling)합니다.
이후 게임 로직 연산, 씬 그래프 업데이트, 애니메이션 평가, 렌더 커맨드 생성(Draw Call), GPU 렌더링, 프레젠트 큐 대기, 최종 디스플레이 주사선 출력(Scanout)이 순차적으로 진행됩니다.

이 구조에서 주사율이 높아지고 GPU 바운드 상황이 발생하면 다음과 같은 병목이 발생합니다:

  1. Early Polling 샘플링 에이징: 프레임 초반에 읽은 입력은 이후 게임 업데이트와 렌더링을 거친 뒤 화면에 나타납니다. 그 사이 지연은 프레임 루프와 큐 상태에 따라 달라집니다.
  2. 렌더 큐 적체(Queue Backpressure): CPU가 GPU보다 앞서 프레임을 제출할 수 있는 구성에서는 대기 프레임이 입력 지연에 더해질 수 있습니다. 버퍼 개수만으로 모든 구성의 지연을 단정할 수는 없습니다.
  3. 물리 고정 틱(TfixedT_{\text{fixed}})과 렌더 주기의 차이: 고정 시간 간격의 물리 업데이트와 가변 렌더 주기는 서로 다르게 진행됩니다. 보간은 시각적 움직임을 부드럽게 할 수 있지만 보간 상태와 샘플링 시점에 따라 지연이 추가될 수 있습니다.

입력 샘플링부터 화면 표시까지의 파이프라인 지연 구조


프레임 루프 아키텍처별 입력 지연 비교

144Hz의 프레임 주기는 약 6.94ms입니다. 아래 표는 지연 측정값이 아니라 각 구조의 일반적인 특성을 비교합니다. 실제 지연은 같은 입력·장면·장치 조건에서 측정해야 합니다.

프레임 루프 방식입력 수집 시점큐 제어고려할 점
전통적 동기 루프보통 프레임 업데이트 전엔진·드라이버 설정에 따름큐가 쌓이는지 측정하고 상한을 조정한다.
분리형 틱 + 보간 루프물리 틱과 렌더 입력이 분리될 수 있음구현에 따라 다름물리 상태 보간은 시각적 지연과 안정성을 함께 측정한다.
Late Polling + 저지연 SDK 루프렌더 제출에 가까운 시점지원 SDK의 API·삽입 위치에 따름최신 입력을 사용할 수 있는 범위와 렌더 파이프라인 제약을 확인한다.

초저지연 프레임 루프를 구현하는 3단계 엔지니어링 가이드

1단계: Late Input Polling(지연 샘플링) 파이프라인 구축

게임의 모든 시스템이 동일한 시점의 입력을 필요로 하지는 않습니다.
예를 들어 인벤토리 UI 토글, 퀘스트 수락 등의 비동기 이벤트는 프레임 시작 시 처리해도 무방하지만 1인칭/3인칭 카메라 회전, 조준선(Crosshair), 즉발 사격 로직은 GPU로 렌더 커맨드를 넘기기 직전의 최신 입력이어야 합니다.

flowchart LR
    subgraph TraditionalLoop [전통적인 프레임 루프]
        direction TB
        A1["Input Polling (Early)"] --> B1["Gameplay Logic"]
        B1 --> C1["Render Command Gen"]
        C1 --> D1["GPU Render & Present"]
    end

    subgraph LowLatencyLoop [저지연 최적화 프레임 루프]
        direction TB
        A2["Gameplay / Physics"]
        A2 --> B2["Late Input Polling (Camera/Aim)"]
        B2 --> C2["Render Transform Update"]
        C2 --> D2["Just-In-Time Present (Reflex Sleep)"]
    end

    %% 서브그래프 간 투명 링크를 주어 좌->우 배치를 강제
    TraditionalLoop ~~~ LowLatencyLoop

다음은 구조를 설명하는 C# 의사 코드입니다. IRawInputDevice와 렌더 파이프라인은 프로젝트별 구현이며, Unity 기본 프레임 루프에 그대로 붙여 넣는 코드는 아닙니다.

public sealed class LowLatencyFrameLoop
{
    private readonly IRawInputDevice mouseDevice;
    private readonly CameraSystem cameraSystem;
    private readonly RenderPipeline renderPipeline;

    public void ExecuteFrame(double deltaTime)
    {
        // 1. 일반 게임 로직 및 애니메이션 업데이트
        UpdateGameLogic(deltaTime);

        // 2. Late Input Polling: 렌더링 커맨드 생성 직전 최신 마우스 델타 수집
        mouseDevice.PollLatestEvents();
        Vector2 latestLookDelta = mouseDevice.ConsumeLookDelta();

        // 3. 카메라 뷰 매트릭스 및 즉발 조준 보정
        cameraSystem.ApplyLateRotation(latestLookDelta);
        cameraSystem.UpdateViewProjectionMatrix();

        // 4. 저지연 렌더링 제출
        renderPipeline.RenderScene(cameraSystem.CurrentViewMatrix);
    }
}

2단계: 프레임 페이싱과 Just-In-Time 렌더 제출 제어

GPU 렌더링 시간이 CPU 프레임 생성 시간보다 길어지는 GPU 바운드 상황에서는 CPU가 쉬지 않고 렌더 커맨드를 제출하여 드라이버 큐에 프레임이 쌓이게 됩니다.
이를 방지하기 위해 렌더 스레드가 GPU 시작 직전에 정확히 깨어나도록 유도하는 Just-In-Time(JIT) 슬립 제어가 필요합니다.

NVIDIA Reflex를 연동하면 지원되는 렌더링 파이프라인에서 SDK가 제공하는 sleep 동작을 적절한 지점에 넣어 CPU와 GPU의 작업 타이밍을 조정할 수 있습니다. AMD Anti-Lag 2는 별도의 제품·통합 경로이므로 Reflex API와 같은 함수로 호출하지 않습니다.

// 의사 코드: 실제 호출은 통합 SDK 및 버전에 맞춰 구현해야 합니다.
// C++ DirectX 12 / Vulkan 프레임 루프 구조 예시
void GameEngine::RunFrame()
{
    // NVIDIA Reflex 프레임 마커 시작: GPU 렌더링 타이밍에 맞춰 CPU 대기
    // 의사 코드: 실제 API 호출은 Streamline/NVAPI 통합 및 버전에 맞춰 작성한다.
    SetReflexMarker(SimulationStart, frameID);

    // 1. 게임플레이 시뮬레이션
    SimulatePhysicsAndEntities();

    SetReflexMarker(SimulationEnd, frameID);

    // 2. Late Input Polling & 렌더 커맨드 생성
    SetReflexMarker(RenderSubmitStart, frameID);
    PollRawMouseAndKeyboard();
    UpdateCameraTransform();
    RecordCommandBuffers();

    // 3. 렌더 커맨드 제출 및 Present
    ExecuteCommandLists();
    SetReflexMarker(RenderSubmitEnd, frameID);

    pSwapChain->Present(0, 0); // VSync OFF (VRR 환경에서 드라이버 처리)
    SetReflexMarker(PresentEnd, frameID);
}

3단계: 가변 주사율(VRR)과 고정 물리 틱 간 서브틱(Sub-tick) 보간

G-Sync 및 FreeSync 환경에서는 모니터 주사율이 프레임 레이트에 맞춰 동적으로 변동합니다(60 Hz∼144 Hz60\text{ Hz} \sim 144\text{ Hz}).

물리 엔진의 fixedDeltaTime을 0.02 s0.02\text{ s}(50Hz)로 고정했을 때 144Hz 화면 갱신 주기(≈0.00694 s\approx 0.00694\text{ s})와의 위상 차이로 인해 화면이 튀는 현상(Stutter)을 방지하려면 누적 잉여 시간(α\alpha)을 이용한 상태 보간(Interpolation)을 구현해야 합니다.

보간 계수 α\alpha는 다음과 같이 계산됩니다.

α=AccumulatorΔtfixed\alpha = \frac{\text{Accumulator}}{\Delta t_{\text{fixed}}}

이 0≤α<10 \le \alpha < 1 값을 이전 물리 상태(St−1S_{t-1})와 현재 상태(StS_t) 사이에 적용해 중간 위치를 렌더링합니다. 일반적인 누적기 보간은 최신 시뮬레이션 상태보다 뒤처진 상태를 그릴 수 있으므로, 부드러움과 입력 지연을 함께 측정해야 합니다.

Srender=(1−α)St−1+αStS_{\text{render}} = (1 - \alpha) S_{t-1} + \alpha S_t

저지연 프레임 루프에서의 입력 샘플링 타이밍 비교


상용 엔진(Unity / Unreal) 적용 방법

Unity 엔진 설정 가이드

  • Input System 패키지 설정: Process Events In Dynamic Update는 보통 각 Update 직전에 입력을 처리하는 설정이며 렌더 제출 직전 폴링을 보장하지 않습니다. 입력 시점을 직접 배치하려면 Process Events Manually와 InputSystem.Update()를 사용하는 커스텀 루프를 검토하고, 입력을 중복 처리하지 않는지 확인합니다.
  • PlayerLoop 커스텀 수정: PlayerLoop.GetCurrentPlayerLoop()를 가져와 Update 또는 PreLateUpdate 단계의 뒤쪽, 즉 PreUpdate.InputUpdate와 별개로 카메라 갱신 직전에 커스텀 인풋 핸들러를 삽입합니다.
  • 저지연 기능 확인: Unity 자체 패키지 이름이나 모든 렌더 파이프라인에 공통인 Reflex 설정은 가정하지 않습니다. 사용 중인 Unity·렌더 파이프라인 및 NVIDIA 통합 문서에서 지원 여부와 설치 절차를 확인하고 적용 후 측정합니다.

Unreal Engine 엔진 설정 가이드

  • 카메라 업데이트 시점 확인: Unreal의 Tick Group은 물리·카메라·렌더 단계에 대한 상대 순서를 정합니다. TG_PostUpdateWork는 카메라가 업데이트된 뒤 실행되므로 일반적인 카메라 회전 입력을 늦게 적용할 위치로 가정하지 말고, 사용 중인 버전의 PlayerCameraManager/카메라 체계에서 지원되는 갱신 지점을 확인합니다.
  • 저지연 플래그 설정: r.GTSyncType 1을 적용하여 게임 스레드와 렌더 스레드의 프레임 동기화 대기를 최적화합니다.
  • NVIDIA Reflex 통합: Unreal의 Reflex 플러그인과 설정 경로는 사용 중인 엔진 버전에서 확인합니다. On + Boost는 지원되는 Reflex 모드이지만 모든 지연 스파이크를 방지한다고 보장하지 않습니다.

자주 묻는 질문 (FAQ)

Q1. 144Hz 게이밍 모니터에서 VSync를 켜면 왜 입력 지연이 체감될 정도로 늘어나나요?

VSync(수직 동기화)는 화면 찢김(Tearing)을 막기 위해 GPU가 렌더링을 마친 프레임을 모니터의 수직 동기 신호(V-Blank) 주기까지 대기시킵니다.
이 과정에서 백버퍼 큐가 가득 차게 되며 CPU는 새로운 프레임을 생성하지 못하고 대기(Backpressure) 상태에 빠집니다.

VSync가 입력 지연을 얼마나 더하는지는 큐 길이, 프레임 타이밍, 버퍼 설정에 따라 달라집니다.
VRR(G-Sync/FreeSync) 환경의 VSync·프레임 제한 조합은 디스플레이 범위와 드라이버 설정에 따라 다릅니다. 목표 프레임 제한과 동기화 설정을 바꾸어 지연, 찢김, 프레임 페이싱을 실제 장비에서 비교합니다.

Q2. 마우스 폴링 레이트(1000Hz vs 4000Hz/8000Hz)가 프레임 루프에 미치는 영향은 무엇인가요?

1000Hz 마우스는 1 ms1\text{ ms}마다 신호를 전달하며 144Hz 루프(6.94 ms6.94\text{ ms}) 내에 약 7개의 입력 이벤트가 발생합니다.
8000Hz 마우스는 이론상 0.125ms 간격으로 보고하며, 144Hz 프레임 간격 동안 약 55개의 보고 시점이 있습니다. 실제 OS 이벤트와 애플리케이션에 전달되는 횟수는 장치·드라이버·운영체제에 따라 달라집니다.
모든 중간 입력 이벤트마다 무거운 게임 로직을 실행하면 비용이 커질 수 있으므로, 필요한 입력을 누적하거나 적절한 시점에 소비하는 방식을 프로파일링합니다.
최적화를 위해서는 중간 이동량을 누적(Accumulate)하여 프레임당 1회의 최종 델타로 압축 처리하는 입력 버퍼링 구조가 필수적입니다.

Q3. Late Polling을 적용할 때 네트워크 동기화 패킷 생성 시점은 어떻게 관리해야 하나요?

클라이언트 렌더링을 위한 Late Polling과 서버 전송용 입력 패킷 생성은 분리해야 합니다.
서버 전송용 입력과 화면 반응용 카메라 입력은 서로 다른 주기와 목적을 가질 수 있습니다. 서버 프로토콜이 틱 기반이면 그 규칙에 맞춰 입력을 시퀀싱하고, 렌더 직전 입력을 네트워크 명령에 섞지 않도록 경계를 명확히 둡니다.


요약 및 결론

120Hz·144Hz 이상의 고주사율 게이밍 환경에서는 렌더링 프레임 레이트를 올리는 것만으로 입력 반응성을 완전히 보장할 수 없습니다.

  1. Late Input Polling을 통해 카메라 및 조준 시스템의 입력 데이터 신선도를 확보하고
  2. 저지연 프레임 페이싱 SDK(Reflex/Anti-Lag)로 GPU 백버퍼 대기 큐를 상시 1프레임 미만으로 억제하며
  3. 서브틱 상태 보간을 통해 가변 주사율 환경에서도 부드럽고 일관된 화면 출력을 구현해야 합니다.

클라이언트 아키텍처 레벨에서 프레임 루프를 위 3단계로 재설계함으로써 e스포츠 및 하이엔드 액션 게임에 걸맞은 극상의 조작감을 완성할 수 있습니다.