언리얼 페스트 서울 2026

06 · Day 1 17:00–17:50

프레임의 여정 — 프레임 타이밍, 동기화, 지연 시간

입력 지연과 히치 내성은 정확히 맞바꾸는 관계다. 그리고 파이프라인이 히치를 흡수해도 지터(judder)는 남는다 — 대부분의 게임이 미묘하게 끊겨 보이는 진짜 이유.

#unreal-engine#performance#latency#frame-timing#vrr#rendering

약 24분 · 6개 중 6번째

세션 개요

한 프레임이 눈에 닿기까지, 그리고 그 대가

지연을 줄이는 모든 방법이 결국 하나의 다이얼을 돌리는 일이라는 것을 보여주는 세션입니다.

유저가 버튼을 누른 순간부터 그 결과가 광자로 화면을 떠나 눈에 닿을 때까지를 따라가는 세션입니다. 엔진이 프레임을 어떻게 겹쳐 돌리고, 그 겹침이 왜 지연을 만들며, 그 지연을 줄이려 할 때 정확히 무엇을 내놓아야 하는지를 순서대로 짚습니다.

한 줄 결론

입력 지연과 히치 내성(hitch resiliency)은 정확히 맞바꾸는 관계입니다. 파이프라인을 길게 잡으면 히치를 흡수하지만 지연이 늘고, 짧게 잡으면 지연은 줄지만 히치가 그대로 화면에 나옵니다. 그리고 파이프라인이 히치를 전부 흡수해도 저더(judder)는 남습니다 — 이것이 대부분의 게임이 미묘하게 끊겨 보이는 진짜 이유입니다.

이 트레이드오프는 발표자의 해석이 아니라 엔진 문서에 그대로 적혀 있는 문장입니다. 에픽의 저지연 프레임 싱크 문서는 rhi.SyncSlackMS를 설명하며 “이 값을 줄이면 엔진 파이프라인이 짧아져 입력 지연은 감소하지만 히치가 프레임 드랍으로 이어질 가능성이 커진다. 반대로 늘리면 파이프라인이 길어져 히치에 대한 내성이 생기지만 입력 지연이 증가한다”고 명시합니다. 세션 전체가 이 한 문장을 플랫폼별로 풀어낸 것이라고 보면 됩니다.

기초 다지기

저더 · 지터 · 테어링 — 먼저 용어를 분리합니다

세 단어가 한국어 자료에서 자주 섞여 쓰이지만 가리키는 현상이 다릅니다. 이 문서는 아래 구분을 끝까지 유지합니다.

  • 저더(judder) — 프레임이 표시되는 간격과 그 프레임이 담고 있는 시간 진행량이 어긋나 움직임이 덜컹거리는 현상입니다. 마이크로소프트의 디스플레이 하드웨어 가이드라인은 24fps 영상을 60Hz에 3:2 풀다운으로 얹으면 “33.3ms, 50ms, 33.3ms, 50ms … 패턴이 생겨 필름 저더를 유발한다”고 설명합니다. 프레임이 하나도 누락되지 않아도 발생한다는 점이 핵심입니다.
  • 지터(jitter) — 간격 자체가 불규칙하게 흔들리는 것을 가리키는 더 일반적인 말입니다. 저더는 “규칙적으로 잘못된 간격”, 지터는 “불규칙한 간격”에 가깝습니다.
  • 테어링(tearing) — 스캔아웃 도중에 버퍼가 교체되어 한 화면에 두 프레임이 섞여 보이는 현상입니다. 타이밍 문제가 아니라 교체 시점 문제입니다.

세션이 끝에서 다루는 문제는 정확히 저더입니다. 프레임 드랍이 0이어도 남는 현상이기 때문입니다.

1

지연은 어디에서 오는가

“프레임의 삶”은 입력 샘플링에서 시작해 광자가 눈에 닿을 때 끝납니다.

한 프레임이 지나는 구간을 늘어놓으면 이렇게 됩니다. 이 중 게임이 통제할 수 있는 구간은 CPU와 GPU, 그리고 스왑 체인 설정 일부뿐입니다. 주변기기의 폴링 간격이나 디스플레이의 픽셀 응답은 게임이 손댈 수 없습니다.

입력에서 광자까지

유저주변기기OS 샘플링게임 CPUGPU컴포지터디스플레이

사진 · 자료

같은 구간을 벤더 문서가 부르는 이름

엔드투엔드 시스템 지연을 주변기기 · PC · 디스플레이 구간으로 분해한 다이어그램
Mouse(HW·USB) → CPU(Sampling · Sim · Render Submission · Driver) → Render Queue → GPU → Composite → Scanout → Display로 쪼갠 뒤, 이를 Peripheral Latency / PC Latency / Display Latency로 묶고 전체를 End-to-End System Latency로 부릅니다. 위 파이프라인 그림과 정확히 같은 구간을 다루고 있어, 세션의 “프레임의 삶”을 벤더 용어와 대조해 읽을 수 있습니다.출처: NVIDIA Technical Blog — Understanding and Measuring PC Latency · © NVIDIA Corporation

한 프레임 안에서 해야 하는 일

단계내용
게임 시뮬레이션게임플레이 코드를 실행해 이번 프레임의 월드 상태를 만듭니다.
렌더링 준비그 상태를 렌더링 씬으로 옮깁니다.
RHI (번역)그래픽스 API용 렌더링 커맨드로 변환해 커맨드 리스트를 구성합니다.
드라이버 → GPU그래픽스 API가 커맨드를 GPU 드라이버로 넘깁니다.
Present그래픽스 API에 “이것이 한 프레임을 구성하는 커맨드 전부”라고 알립니다.

이 다섯 단계를 순차적으로 돌리면 60fps의 16.666ms 예산에 절대 들어가지 않습니다. 그래서 엔진은 이들을 서로 다른 스레드에 얹어 겹칩니다 — 5장에서 다룹니다.

기초 다지기

click-to-photon과 PC Latency는 다른 값입니다

지연을 이야기할 때 어느 구간을 재는 것인지 먼저 합의해야 합니다. 벤더 문서는 이 구분을 명시적으로 정의해 두었습니다. NVIDIA의 기술 문서는 “주변기기 지연 + PCL + 디스플레이 지연을 합치면 엔드투엔드 시스템 지연의 전체 그림이 된다”고 적고, PC Latency(PCL)를 “입력에서 프레임 시작까지(I2FS), 프레임 시작에서 Present까지(FS2P), Present에서 표시까지(P2D)의 평균 합”으로 정의합니다.

즉 흔히 말하는 click-to-photon은 마우스 스위치부터 픽셀이 실제로 색을 바꿀 때까지를 재는 물리 측정이고, PC Latency는 그중 PC가 책임지는 구간입니다. 게임 개발자가 개선할 수 있는 것은 후자이고, 유저가 체감하고 리뷰어가 측정하는 것은 전자입니다. 같은 게임에서 두 숫자가 다르게 나오는 이유가 여기 있습니다.

2

스왑 체인 — 버퍼는 몇 장이고, 언제 교체하는가

프론트 버퍼 / 백 버퍼, 버퍼 개수, 그리고 VSync.

프론트 버퍼와 백 버퍼

  • 게임은 그림을 그릴 서피스(버퍼)를 가집니다. 렌더 타겟 텍스처, 혹은 아티스트가 그리는 캔버스라고 생각하면 됩니다.
  • GPU는 이 버퍼를 스캔해 디스플레이로 보냅니다. 지금 스캔되고 있는 버퍼가 프론트 버퍼입니다.
  • 프론트 버퍼에 직접 그리지 않습니다. 그리는 도중의 그림이 화면에 나타나면 곤란하기 때문입니다. 그래서 백 버퍼에 그린 뒤 교체합니다.
플랫폼기본 버퍼링
콘솔 · 모바일더블 버퍼링 — 버퍼 2장
PC한 장을 더 얹어 총 3장(트리플 버퍼링)

버퍼가 한 장 늘면 파이프라인이 한 프레임 더 깊어집니다 — 히치 내성은 올라가고 입력 지연도 함께 올라갑니다. 이 문서 전체를 지배하는 그 트레이드오프의 가장 단순한 형태입니다.

기초 다지기

Flip Model — 버퍼 2장이 하한선인 이유

PC에서 이 버퍼 배열을 실제로 정의하는 것은 DXGI 스왑 체인입니다. 마이크로소프트 문서는 플립 모델 스왑 체인의 요구사항 첫 항목으로 “버퍼 개수는 최소 2 이상이어야 한다”를 들고, 성능 저하를 피하려면 BufferCount를 2에서 16 사이로 두라고 안내합니다. 같은 문서의 예제 코드는 BufferCount = 2 옆에 “지연을 최소화하려면 더블 버퍼링을 사용한다”는 주석을 달아둡니다.

즉 “콘솔은 2장, PC는 3장”이라는 세션의 설명은 API가 강제하는 값이 아니라 플랫폼별 기본 선택입니다. 그리고 그 선택이 곧 파이프라인 깊이의 하한과 상한을 정합니다.

VSync — 교체 시점을 수직 동기 신호에 맞추기

수직 동기(VSync)를 기다린 뒤 버퍼를 교체하면 전환이 매끄럽습니다. VSync 신호는 한 프레임의 스캔이 끝나는 시점에 발생합니다. 기다리지 않고 아무 때나 교체할 수도 있지만, 그러면 스캔 도중에 교체되어 화면이 찢어집니다(테어링, tearing).

r.VSync
  0 : VSync is disabled. (default)
  1 : VSync is enabled.

rhi.SyncInterval
  0 : Unlocked (present immediately)
  1 : Present every vblank interval
  2 : Present every 2 vblank intervals
  3 : etc...

위 설명문은 에픽 공식 콘솔 변수 레퍼런스의 표기를 그대로 옮긴 것입니다. rhi.SyncInterval은 “지원되는 RHI에서 VSync의 주기를 결정하며, 60Hz 디스플레이에서는 16.66ms의 배수”로 정의됩니다.

VSync 켬

대부분의 콘솔 게임이 강제하는 쪽

  • 테어링 없음
  • 스왑 체인이 큐 역할을 해 히치 내성이 생김
  • 대신 입력 지연 증가

VSync 끔

r.VSync 0

  • 입력 지연 감소 — 줄을 세우지 않고 즉시 넘김
  • 테어링 발생
  • 히치 내성 감소

발표자 권장 — 게임을 만든다면 VSync를 기본 ON으로 두고, 유저가 끌 수 있는 옵션을 제공할 것.

짚어둘 점

엔진 기본값과 게임 기본값은 다릅니다

공식 레퍼런스의 r.VSync 기본값은 0(꺼짐)입니다. “VSync를 기본 ON으로 두라”는 권장은 엔진 기본값을 그대로 쓰라는 뜻이 아니라, 게임의 기본 설정을 그렇게 잡으라는 뜻입니다. 실제 출시 빌드는 대개 게임 유저 설정(GameUserSettings)이 이 값을 덮어씁니다. 프로파일링 중 “왜 테어링이 보이지” 싶을 때 먼저 확인할 지점입니다.

한편 테어링을 의도적으로 허용하는 경로도 API에 정식으로 있습니다. 마이크로소프트 문서는 DXGI_PRESENT_ALLOW_TEARING을 “sync interval 0에서만 사용할 수 있으며, 테어링이 지원되고 애플리케이션이 윈도우 모드(보더리스 풀스크린 포함)라면 sync interval 0을 쓸 때 항상 이 플래그를 넘기는 것을 권장한다”고 안내합니다.

Unreal Insights에서 flip을 눈으로 확인하기

관련 콘솔 변수를 켜면 Insights에 flip 마커가 표시되어, 어느 프레임 번호가 프론트 버퍼로 교체됐고 어느 프레임이 miss였는지를 확인할 수 있습니다. 게임 프레임 번호와 같은 번호를 쓰기 때문에 파이프라인 전체를 하나의 번호로 추적할 수 있고, 전용 트랙도 추가 예정이라고 밝혔습니다.[1]

3

컴포지터 — 한 프레임을 공짜로 되찾는 구간

OS가 창들을 합성하는 데 한 프레임이 듭니다. 가능하면 건너뛰고 싶습니다.

컴포지터는 게임 · 영상 플레이어 · 브라우저 · 스트리밍 오버레이 같은 여러 창을 하나의 그래픽 버퍼로 합쳐 디스플레이로 보내는 일을 합니다. 윈도우에서는 DWM(Desktop Window Manager)이 그 역할입니다. 문제는 이것도 실제 GPU 작업이고, 그리는 데 한 프레임이 걸린다는 점입니다.

플랫폼상황
콘솔컴포지터가 있긴 하지만 위에 뭔가 올라오지 않는 한 항상 건너뜁니다. 게임에 GPU 독점 접근을 주므로 신경 쓸 필요가 없습니다.
Windows여러 앱이 동시에 돌 수 있으므로 합성이 필요합니다 → 건너뛸 방법을 찾아야 합니다.

어떻게 건너뛰는가 — 과거와 현재

합성이 일어나는 경로

게임 백 버퍼DWM 합성스캔아웃

합성을 건너뛴 경로 (Independent Flip)

게임 백 버퍼 스캔아웃

과거에는 Exclusive Fullscreen(전체 화면 독점)밖에 방법이 없었습니다. 모드 전환 때 화면이 검게 깜빡이고, Alt+Tab을 하면 다시 전환 과정을 거쳐야 했습니다.

현재는 Windows 10 이상 + DirectX 12 + Flip Model 스왑 체인(기본값) 조합이면 Borderless Fullscreen 모드만으로도 자동으로 컴포지터를 건너뜁니다. 윈도우가 “화면에 이 게임밖에 없으니 합성할 필요가 없다”를 감지하면 활성화됩니다.

기초 다지기

Independent Flip — 마이크로소프트가 직접 쓴 문장

이 동작에는 정식 이름이 있습니다. 마이크로소프트 문서는 “창과 버퍼 구성에 따라 데스크톱 합성을 완전히 우회하고, 전체 화면 독점과 동일한 방식으로 애플리케이션 프레임을 화면으로 직접 보내는 것이 가능하다”고 적고, 이어서 “스왑 체인이 DirectFlip되면 DWM은 잠들 수 있다 … 애플리케이션 프레임이 전체 화면 독점과 동일한 효율로 독립적으로 화면에 직접 전달된다. 이것이 Independent Flip이다”라고 설명합니다.

그래서 세션의 “Exclusive Fullscreen은 사실상 유물”이라는 주장은 벤더 문서와 결이 같습니다. 같은 문서가 “플립 모델 보더리스 윈도우의 이점은 더 빠른 Alt+Tab과 최신 디스플레이 기능과의 통합이므로, 애플리케이션이 정말 전체 화면 독점 모드를 필요로 하는지 재고할 만하다”고 직접 권고합니다. 다만 SetFullscreenState API 자체가 폐기 예고된 것은 아니라는 점은 구분해둘 필요가 있습니다.

MPO — 창모드에서도 OS 합성을 건너뛰는 하드웨어 경로

추가로 MPO(Multiplane Overlay)라는 하드웨어 기능이 있습니다. 게임이 창모드여도 활성화될 수 있고, GPU가 합성을 대신 처리하므로 OS 쪽 단계를 항상 건너뜁니다. 마이크로소프트 문서는 이 경로를 “DWM이 애플리케이션을 위해 전용 하드웨어 스캔아웃 플레인을 예약할 수 있다”고 설명합니다. MPO 자체는 Windows 8.1(WDDM 1.3)에 도입된 정식 기능으로 드라이버 문서에 명시되어 있습니다.

실용 팁 하나

발표에서는 NVIDIA에서는 디스플레이 설정상 “주 모니터”에서만 MPO가 활성화된다고 안내했습니다. 어느 포트에 꽂혔는지는 무관하며, 보조 모니터에서 게임하던 사람이 주 모니터로 옮기는 것만으로 프레임 하나만큼 지연이 줄어든다는 이야기입니다.[2]

확인 방법 — PresentMon의 Present Mode 컬럼

지금 내 게임이 합성을 거치고 있는지는 추측이 아니라 측정으로 알 수 있습니다. 인텔의 PresentMon이 Present Mode 컬럼으로 그 값을 그대로 보여줍니다. 아래 문구는 인텔 저장소의 문서 표기입니다.

Present Mode 값의미
Hardware: Legacy Flip앱이 화면 소유권을 가져가 매 프레임 표시 서피스를 교체하는 상태 — 옛 Exclusive Fullscreen
Hardware: Independent Flip앱이 화면 소유권을 갖지 않으면서도 매 프레임 표시 서피스를 교체하는 상태 — 합성 우회 성공
Hardware Composed: Independent Flip위와 같으면서 하드웨어 오버레이 플레인을 부여받은 상태 — MPO 경로
Composed: Flip앱이 창모드로 플립 모델 스왑 체인을 쓰지만 DWM과 서피스를 공유해 합성되는 상태 — 한 프레임을 손해 보는 쪽

Composed: Copy with GPU GDI / CPU GDI 같은 더 느린 값들도 정의되어 있습니다. 목표는 Independent Flip 계열에 머무는 것입니다.

사진 · 자료

PresentMon 오버레이 — Frame Time과 GPU Busy를 분리해서 본다

PresentMon 오버레이가 Frame Time과 GPU Busy를 그래프로 표시한 화면
Frame Time 7.24ms에 GPU Busy 6.07ms. 두 값의 차이가 곧 “GPU가 일하지 않은 시간”이고, 그 안에 VSync 대기와 파이프라인 대기가 들어 있습니다. PresentMon은 이 외에도 PC Latency, Display Latency, Click To Photon Latency, Animation Error 같은 지표를 정의해 제공합니다.출처: Intel — PresentMon · © Intel Corporation

4

Secondary Upscale — Exclusive Fullscreen의 진짜 대안

해상도를 낮추려고 독점 모드를 쓰는 것이라면, 엔진 안에서 하는 편이 낫습니다.

컴포지터 스킵 말고도 Exclusive Fullscreen을 쓰는 이유가 하나 더 있습니다. 해상도를 낮춰 렌더링할 픽셀 수를 줄이려는 것입니다. 그런데 이 경우 업스케일을 모니터가 담당하게 됩니다.

모니터가 업스케일

해상도를 바꿔 독점 모드로 던지는 경우

  • 저렴한 공간적(spatial) 업스케일러
  • 현재 프레임 정보만 사용
  • UI까지 함께 늘어나 흐려짐

GPU가 업스케일

엔진의 Secondary Upscale

  • 훨씬 좋은 품질의 공간적 업스케일 가능
  • 방식을 골라 쓸 수 있음
  • 업스케일 후에 UI를 그림 → UI가 네이티브 해상도로 선명

발표자 권장 — Exclusive Fullscreen을 제공할 거라면 대신 Secondary Upscale을 쓸 것. 더 잘 보이고 성능은 동등하다.

언리얼의 2단계 업스케일

렌더 해상도에서 출력 해상도까지

내부 해상도 렌더Primary — 시간적 또는 공간적 업스케일Secondary — 공간적 업스케일UI 합성
단계성격무엇이 여기 들어가는가
Primary Upscale시간적(TSR·DLSS 등), 꺼져 있으면 공간적TSR이 여기서 일어납니다. DLSS · FSR · XeSS 같은 서드파티 업스케일러도 이 단계에 붙습니다.
Secondary Upscale선택적 공간적(spatial)방식은 r.Upscale.Quality로 지정합니다. 그리고 이 뒤에 UI를 그립니다.
r.ScreenPercentage                        ; 1차(내부) 해상도 비율
r.SecondaryScreenPercentage.GameViewport  ; 2차 해상도 비율 (0 = DPI 스케일로 자동)
r.Upscale.Quality
  0 : Nearest filtering
  1 : Simple Bilinear
  2 : Directional blur with unsharp mask upsample
  3 : 5-tap Catmull-Rom bicubic, approximating Lanczos 2 (default)
  4 : 13-tap Lanczos 3
  5 : 36-tap Gaussian-filtered unsharp mask (very expensive)

값 목록은 에픽 공식 문서 「Screen Percentage with Temporal Upscale」의 표기 그대로입니다. 같은 문서가 r.SecondaryScreenPercentage.GameViewport를 “게임 뷰포트의 2차 스크린 퍼센티지를 덮어쓴다. 0이면 100 / DPI 스케일 팩터로 자동 계산(기본값)”으로 정의합니다.

기초 다지기

UI가 선명해지는 이유는 순서 하나 때문입니다

공식 문서는 primary와 secondary를 이렇게 갈라 설명합니다 — “Primary Spatial Upscaling은 프레임을 더 낮은 해상도로 렌더한 뒤 UI가 그려지기 전에 업스케일하는 것이고, Secondary Spatial Upscaling은 Primary와 독립적으로 마지막에 한 번 더 공간적 업스케일 패스를 수행하는 것”입니다.

그리고 2차 비율을 다룰 때의 실무 조언도 문서에 그대로 있습니다 — “UI가 항상 대상 플랫폼의 네이티브 해상도로 렌더되도록 1080p나 4K에 두는 것이 가장 좋다”. 세션이 강조한 “업스케일을 먼저 하고 UI를 나중에 그린다”가 바로 이 순서를 말하는 것입니다.

예외 — 메모리 제약이 걸리는 기기

GPU가 매우 약한 노트북에서 4K 텍스처 등으로 메모리 제약이 걸리는 경우에는 오히려 느려질 수 있습니다. 그래서 발표자는 “업스케일을 GPU에서 할지 모니터에서 할지”를 유저에게 선택하게 하라고 권했습니다.

5

파이프라인과 “브레이크” — 왜 겹치고, 왜 기다리는가

겹치면 예산이 늘어납니다. 대신 누군가는 기다려야 합니다.

60fps라면 한 프레임에 16.666ms가 주어집니다. 그 안에 게임 시뮬레이션 · 렌더링 오퍼레이션 · RHI · GPU 렌더링을 모두 끝내야 합니다. 하지만 요즘 엔진은 이걸 순차적으로 하지 않습니다. 각 단계를 다른 스레드에 얹어 서로 겹치게(interleave) 합니다.

  • 각 단계가 실제로 쓸 수 있는 시간이 두 배 이상으로 늘어납니다.
  • 한 프레임을 완성하는 데 2프레임 이상이 걸려도, 겹쳐 쌓으면 여전히 60fps를 낼 수 있습니다.
  • 대가는 지연입니다. 지금 시뮬레이션한 프레임이 화면에 나오는 것은 몇 프레임 뒤입니다.

도해

파이프라이닝 — 같은 프레임이 여러 슬롯에 걸쳐 흐른다

게임 스레드

A
B
C

렌더 스레드

A
B
C

RHI 스레드

A
B
C

GPU

A
B

디스플레이

A

프레임 슬롯 →

프레임 A프레임 B프레임 C
세로로 읽으면 한 시점에 서로 다른 프레임이 서로 다른 단계에 동시에 존재합니다. 가로로 읽으면 프레임 A가 시뮬레이션되고 나서 화면에 표시되기까지 다섯 슬롯이 걸립니다. 이 가로 길이가 곧 입력 지연이고, 세로 두께가 곧 히치 내성입니다.

사진 · 자료

위 도해를 실제 프로파일로 본 화면 — Timing Insights

Unreal Insights의 Timing Insights 창. GPU, GameThread, RHIThread, RenderThread 트랙이 나란히 놓여 있다
6번 영역이 스레드 트랙입니다. GPU · GameThread · RHIThread · RenderThread가 각각의 레인을 가지고 같은 시간축 위에 놓여 있어, 어느 스레드가 어느 시점에 무엇을 하고 있었는지를 그대로 읽을 수 있습니다. 위 도해는 이 화면을 프레임 슬롯 단위로 단순화한 것입니다.출처: Epic Games — Unreal Insights 공식 문서 · © Epic Games, Inc.

동기화가 깨지면 — “파이프라인의 브레이크”

게임을 일시정지하거나, 메뉴로 들어가거나, 게임 스레드가 갑자기 빨라지면 게임 스레드가 혼자 앞으로 계속 달려나갑니다. 그래서 파이프라인의 각 부분이 나머지보다 너무 앞서지 않도록 기다릴 수 있어야 합니다. 발표자는 이를 “파이프라인의 브레이크”라고 불렀습니다.

주체기다리는 대상
GPU스왑 체인을 통해 VSync
RHI 스레드이전 GPU 프레임의 끝, 그리고 그래픽스 API의 Present 호출 — 쓸 수 있는 버퍼가 없으면 이전 프레임의 하드웨어 present 쿼리를 기다립니다.
게임 스레드이전 렌더링 프레임의 시작, 그리고 2프레임 전[3]

게임 스레드가 “느린” 것은 성능 문제가 아니라 설계입니다 — 다른 것들이 지나치게 앞서나가는 것을 막는 장치입니다.

이 대기 관계를 실제로 정의하는 것이 r.OneFrameThreadLag입니다. 공식 레퍼런스의 정의는 “렌더링 스레드가 게임 스레드보다 한 프레임 뒤처지는 것을 허용할지 여부(0이면 비활성, 그 외에는 활성)”이고, 기본값은 1입니다. 저지연 프레임 싱크 문서는 여기에 배경을 덧붙입니다 — “이 CVar가 켜져 있으면(기본값) 게임 스레드가 렌더링 스레드보다 온전한 한 프레임 이상 앞서가지 않도록 보장된다”.

더 깊이

병렬 렌더링이 지연을 늘려버린 역사

지금의 구조에는 이유가 있습니다. 같은 공식 문서가 그 경위를 직접 설명합니다. UE4의 병렬 렌더링 이전에는 게임 스레드가 렌더링 스레드를 기다리고, 그 렌더링 스레드가 드라이버/OS에 의해 Present에서 막히는 구조였으므로 동기화가 올바르게 작동했습니다.

그런데 병렬 렌더링이 도입되면서 Present를 호출하는 주체가 RHI 스레드로 바뀌었습니다. 드라이버가 스로틀하는 것은 RHI 스레드인데 게임 스레드는 여전히 렌더링 스레드만 보고 있으니, 두 스레드가 GPU와 실제 flip보다 훨씬 앞서 달릴 수 있게 됐습니다. 문서가 든 숫자가 직설적입니다 — “rhi.SyncInterval을 2로 둔 30Hz 타이틀에서 최악의 경우 입력 지연이 130ms까지 갈 수 있다”. 이 문제를 풀기 위해 게임 스레드를 RHI 스레드에 붙일 수 있게 만든 것이 7장의 r.GTSyncType입니다.

짚어둘 점

Insights에서 VSync 대기가 안 보이는 함정

GPU가 VSync를 기다리는 구간은 아무것도 하지 않는 빈 구멍(bubble)으로만 보이고, 엔진이 “이건 VSync 대기입니다”라고 말해주지 않습니다. 내부 RDG 이벤트를 열어보면 엉뚱하게 포스트 프로세스 필터 같은 이름이 표시되기도 하는데, 실제 답은 프레임 끝의 대기입니다.

공식 Insights 레퍼런스의 트레이스 채널 목록에도 flip이나 present를 이름에 가진 채널은 없습니다. 프레임 경계를 다루는 채널은 Frame(“Game and Rendering frames.”)이고, RHI 쪽은 RHICommands, RenderCommands, RenderTrace가 있습니다. 즉 “빈 구멍의 정체를 채널 이름으로 알아낼 방법이 기본 제공되지 않는다”는 것이 현재 상태이고, 그래서 앞 장의 flip 마커가 의미를 갖습니다.

Oversubscription — 대기 중인 코어를 놀리지 않기

질문이 가장 많이 들어오는 항목이라고 소개된 개념입니다. 시스템에 하드웨어 스레드가 여러 개 있는데 워커 스레드가 무언가(예: GPU)를 기다리는 동안 그 코어는 아무것도 하지 않고 낭비됩니다. Oversubscription은 엔진에게 “지금 블록됐으니 스레드를 하나 더 띄워 다른 일을 시켜라”고 알려주는 방식입니다. 결과적으로 워커 스레드가 유휴 상태가 되어도 컴퓨터의 모든 코어를 계속 활용할 수 있습니다.[4]

여기서 기다리는 대상이 “큐(Queue)”가 아니라 “디큐(Dequeue)”라는 점을 발표에서 강조했습니다 — 커맨드를 넣는 것이 막히는 게 아니라, 앞의 것이 빠져나가기를 기다리는 것입니다.

6

Input Latency와 Hitch Resiliency — 같은 다이얼의 양쪽

지연은 낭비가 아니라 값을 치르고 산 버퍼입니다.

언리얼이 “입력 지연”이라고 부르는 구간

측정 구간
일반적 정의 (click-to-photon)버튼을 누른 순간부터 화면에 표시되어 빛이 눈에 닿을 때까지
언리얼 stat unit의 Input입력이 실제로 샘플링된 시점부터, 백 버퍼가 프론트로 flip될 때까지[5]

입력이 이전 프레임 시작 직후에 들어오면 그 프레임은 그것을 소비하지 못합니다. 다음 프레임이 소비한 시점부터가 카운트 시작입니다.

기초 다지기

stat unit이 공식적으로 보여주는 줄들

공식 문서 기준으로 stat unit은 Frame · Game · Draw · GPU · RHIT · DynRes를 표시합니다. 각각의 정의는 이렇습니다.

  • Frame — “게임의 한 프레임을 만들어내는 데 쓰인 전체 시간”
  • Game — 이 값이 Frame에 가까우면 병목이 게임 스레드
  • Draw — 이 값이 Frame에 가까우면 병목이 렌더링 스레드
  • GPU — “비디오 카드가 씬을 렌더하는 데 걸리는 시간”
  • RHIT — RHI 스레드 시간, 프레임에 동기화됨
  • DynRes — Primary 스크린 퍼센티지 × Secondary 스크린 퍼센티지

Frame과 Game·Draw·GPU를 비교하는 것만으로 “누가 병목인가”를 먼저 가르고, 그다음에 Insights로 내려가는 것이 표준 순서입니다.

Hitch Resiliency — 지연의 대가로 받는 것

파이프라인이 가득 차 있으면 작은 히치를 흡수하는 버퍼 역할을 합니다. 비용은 두 가지입니다.

  1. 입력 지연.
  2. 게임 스레드 CPU 예산. 파이프라인을 다시 채우려면 따라잡을 여력이 있어야 하기 때문입니다.
파이프라인에 프레임 1~8이 동시에 존재
프레임 9가 오래 걸림  →  1프레임 미스, 2프레임 미스, 3프레임 미스
디스플레이는 지금 프레임 4를 그리는 중  →  앞에 큰 공백이 다가온다
  → 프레임을 드랍할까?
아니다. 이후 빠르게 달려서 프레임 6, 7, 8을 처리
  → 유저가 단 한 프레임의 드랍도 눈치채기 전에 파이프라인을 다시 채운다

긴 파이프라인

버퍼를 많이 쌓는 쪽

  • 히치 내성 ↑ — 작은 히치가 화면에 나오지 않음
  • 입력 지연 ↑
  • 따라잡기용 CPU 예산이 필요

짧은 파이프라인

즉시 넘기는 쪽

  • 입력 지연 ↓
  • 히치 내성 없음 — 예산을 조금만 넘겨도 유저가 바로 느낌
  • 델타 타임이 매우 고르다

다이얼은 하나입니다. 게임 성격상 히치가 크게 문제되지 않는다면 내성을 팔아 지연을 사면 됩니다.

7

입력 지연을 줄이는 세 가지 방법

프레임 시간을 줄이거나, 시작을 늦추거나, 파이프라인 자체를 짧게 만듭니다.

방법 ① — 프레임레이트를 올린다

파이프라인의 최대 프레임 “개수”는 고정이지만, 프레임 “시간”은 고정이 아닙니다. 같은 깊이의 파이프라인이라도 한 프레임이 짧아지면 그 깊이가 차지하는 총 시간이 줄어듭니다.

프레임레이트프레임 시간파이프라인 6프레임 기준 지연
30 FPS33.3ms200ms — 버튼을 누르고 0.2초, 확실히 체감됩니다
60 FPS16.7ms100ms
120 FPS8.3ms50ms

히치 내성과 병렬성을 그대로 유지하면서 지연만 줄이는 유일한 방법이기도 합니다.[6]

간단한 방법이라고 했지, 쉬운 방법이라고는 안 했습니다.

— 발표자

반대로 프레임레이트에 상한을 씌우는 쪽도 지연에 영향을 줍니다. 공식 레퍼런스의 t.MaxFPS 정의는 “주어진 값으로 FPS를 제한한다. 0 이하로 두면 제한하지 않는다”입니다.

방법 ② — 게임 스레드 시작을 늦춘다 (스로틀링)

게임 시뮬레이션이 화면 표시보다 너무 일찍 일어나서 지연이 커지는 것이므로, 작업량은 그대로 두고 시작 시점만 뒤로 밀면 지연이 줄어듭니다. 이상적으로는 매 VSync마다, 프레임의 전체 작업이 VSync 직전에 끝나도록 배치하는 것입니다.

도해

같은 작업량, 다른 시작 시점

기본 (즉시 시작)

시뮬
렌더 · RHI · GPU
대기

스로틀 (지연 시작)

슬랙
시뮬
렌더 · RHI · GPU

한 프레임 구간 →

게임 시뮬레이션이후 파이프라인남는 슬랙
위는 프레임 시작과 함께 곧바로 시뮬레이션을 돌리는 경우, 아래는 VSync 직전에 끝나도록 시작을 밀어둔 경우입니다. 작업 블록의 길이는 같지만 아래쪽이 입력 샘플링 시점과 표시 시점 사이가 더 짧습니다. 대신 아래쪽에는 여유가 거의 없어, 예산을 넘기면 그대로 프레임이 늦습니다.

방식 A — 고정 지연

콘솔용

CVar
r.GTSyncType 2
동작
게임 스레드를 GPU 스왑 체인 flip에 동기화
다이얼
rhi.SyncSlackMS
장점
매우 좋은 델타 타임, 최소 입력 지연
단점
히치 내성 없음 — 예산을 조금만 넘겨도 유저가 바로 느낌

방식 B — 적응형

PC용

수단
서드파티 저지연 SDK
동작
이전 프레임들을 기반으로 자동 조정 → 슬랙을 직접 정할 필요 없음
골칫거리
여러 SDK를 각각 구현해야 함

방식 C — 파이프라인 단축

발표자가 딱히 권하지는 않는 방법

CVar
r.OneFrameThreadLag 0 · r.GTSyncType 1
효과
기다리는 대상을 줄여 파이프라인을 물리적으로 짧게
대가
병렬성 감소 또는 내성 감소

r.GTSyncType — 공식 정의와 값

r.GTSyncType
  0 : Sync the game thread with the render thread (default)
  1 : Sync the game thread with the RHI thread
  2 : Sync the game thread with the GPU swap chain flip (only on supported platforms)

rhi.SyncSlackMS
  "Increases input latency by this many milliseconds, to help performance
   (trade-off tunable). Gamethread will be kicked off this many milliseconds
   before the vsync"

두 인용문 모두 에픽 공식 콘솔 변수 레퍼런스의 표기입니다. 저지연 프레임 싱크 문서는 “지원하지 않는 플랫폼에서 r.GTSyncType 2는 모드 1로 폴백된다”고 명시합니다.

설정방향결과
rhi.SyncSlackMS줄이면파이프라인이 짧아져 입력 지연 ↓, 히치가 프레임 드랍으로 이어질 가능성 ↑
rhi.SyncSlackMS늘리면파이프라인이 길어져 히치 내성 ↑, 입력 지연 ↑

이 표의 내용은 해석이 아니라 공식 문서의 서술 그대로입니다. 문서는 이어서 “허용 가능한 프레임레이트를 유지하는 선에서 이 값을 최대한 줄이는 것을 목표로 하라”고 권합니다.

더 깊이

공식 문서가 제시한 실제 숫자

저지연 프레임 싱크 문서에는 30Hz 타이틀의 구체적인 설정 예시와 그 결과가 함께 실려 있습니다.

rhi.SyncInterval 2 · r.GTSyncType 2 · r.OneFrameThreadLag 1 · r.Vsync 1 · rhi.SyncSlackMS 0 조합에서 “최선의 경우 입력 지연은 약 66ms(30Hz 두 프레임)”이고, rhi.SyncSlackMS를 10으로 올리면 약 76ms가 됩니다. 슬랙을 10ms 늘렸더니 지연이 정확히 10ms 늘어난 것입니다 — 이 CVar가 왜 “다이얼”인지가 이 숫자에서 그대로 드러납니다.[7]

같은 문서는 r.GTSyncType 2가 60Hz 타이틀(rhi.SyncInterval 1)에서도 동작하지만, 프레임레이트가 두 배라 지연이 이미 절반이므로 체감 이득은 30Hz보다 작다고 덧붙입니다.

고정 지연은 하드웨어가 고정된 곳에 씁니다

프레임이 얼마나 걸릴지 알 수 있으므로 콘솔별로 값을 잡으면 됩니다. 발표에서는 지원 플랫폼으로 PS4 · PS5 · Xbox One · Xbox Series X · Switch 1 · Switch 2를 들었습니다. 반면 PC · 모바일처럼 하드웨어가 변하는 환경에서는 고정 예산을 지키기 어렵습니다. 설정할 때는 약간의 여유를 남겨야 하고, 넘기면 프레임을 드랍합니다.

PC의 적응형 경로 — 벤더 SDK

PC에서는 하드웨어를 알 수 없으므로 슬랙을 상수로 둘 수 없습니다. 그래서 이전 프레임들을 보고 지연을 자동 조정하는 벤더 SDK를 쓰게 됩니다. 세 벤더 모두 같은 아이디어를 각자의 이름으로 제공합니다.

솔루션공식 문서가 설명하는 동작
NVIDIA Reflex“Reflex Low Latency 모드는 CPU와 GPU 파이프라인을 동기화해 just-in-time 렌더링을 하도록 만든다”
AMD Anti-Lag 2“AntiLag 1과 비슷한 일을 하지만, 지연을 삽입하는 지점이 게임 로직 내부의 최적 위치, 즉 유저 입력을 샘플링하기 바로 직전으로 옮겨졌다”
Intel XeLL“프레임별 타이밍 정보를 받아 다음 프레임 작업을 시작하기 전에 적용할 CPU 지연을 계산한다 … 입력에서 화면까지의 지연이 감소한다”

세 문장 모두 “게임 스레드 시작을 뒤로 미룬다”는 방법 ②의 아이디어를 그대로 서술합니다. 즉 r.GTSyncType 2의 고정 슬랙을 적응형으로 바꾼 것이 벤더 SDK입니다.

여러 SDK를 각각 구현해야 한다는 것이 실무의 골칫거리입니다. 발표자는 PC 점유율이 가장 큰 NVIDIA Reflex는 최소한 넣고, 여력이 되면 나머지도 추가하라고 권했습니다.

사진 · 자료

적응형 스로틀링이 “보고” 있는 것 — 프레임 ID가 붙은 마커

게임이 Sim start · PCL PING · Render/Present 마커를 프레임 ID와 함께 넘기고, 그래픽스 API가 Present와 Display flip 이벤트를 넘기는 구조 다이어그램
게임이 Sim start / PCL PING / Render·Present 마커를 프레임 ID와 함께 내보내고, 그래픽스 API·OS 쪽에서 Present와 Display flip 이벤트가 올라옵니다. 이렇게 같은 프레임 ID로 입력 · 시뮬레이션 · Present · 실제 flip을 묶는 구조가 있어야 적응형 조정이 가능합니다. 앞서 본 Insights의 flip 마커가 노리는 것과 같은 종류의 연결입니다.출처: NVIDIA Technical Blog — Understanding and Measuring PC Latency · © NVIDIA Corporation

방법 ③ — 파이프라인 자체를 짧게 (권장되지 않음)

CVar효과대가
r.OneFrameThreadLag 0기본값 1에서는 렌더 스레드가 게임 스레드보다 한 프레임 뒤처지는 것이 허용되어 둘이 동시에 진행됩니다. 0으로 끄면 게임 스레드가 현재 프레임의 렌더 스레드가 끝날 때까지 기다린 후 진행합니다.병렬성 감소 — 게임 스레드와 렌더 스레드가 겹칠 수 없으므로 사실상 하드웨어 스레드 하나를 낭비합니다.
r.GTSyncType 12프레임이 아니라 이전 1프레임(RHI 스레드)만 기다립니다 → 파이프라인이 더 짧아집니다.지연은 더 줄지만 내성도 더 줄어듭니다.

8

앞으로 — 새 프레임 페이싱 모드

고정 슬랙과 적응형의 장점을 합치려는 작업이 진행 중입니다.

이거 끔찍하죠. 복잡하고요. 이게 지금 우리가 가진 최선인가? 맞습니다. 그리고 여러분 생각에 동의합니다. 이래서는 안 되죠.

— 발표자

현재 에픽이 프레임 페이싱을 리워크 중이며, 계획은 적응형 프레임 페이싱 + 조절 가능한 타깃 슬랙(target slack)을 가진 새 모드입니다. 서드파티 솔루션과 유사하게 동작하면서 최소 입력 지연과 최대 병렬성을 함께 유지하는 것이 목표입니다.

타깃 슬랙만 설정 (예: 5ms)
  → 어떤 프레임이 작업을 조금 더 하면
  → 적응형 타이머가 개입해 바깥으로 조금 넓혀서 슬랙을 유지
게임 시간 변동권장
작다슬랙을 작게 유지
크다슬랙을 늘리거나 기존 모드 사용

아직 어느 버전에 들어갈지는 정해지지 않았고, 준비되면 공개 원문 아티클을 업데이트하겠다고 밝혔습니다.[8]

더 깊이

구현이 어려운 진짜 이유

Q&A에서 나온 답이 이 작업의 난점을 정확히 짚습니다. 언리얼이 OS와 타이밍을 조율하는 통로는 전부 스왑 체인입니다. 그래서 GPU에 프레임 타이밍을 직접 물어볼 수 없고, 반드시 그래픽스 API를 거쳐야 합니다. DirectX는 비교적 최근에야 지원하기 시작했고, VRR이나 VSync 상태에 따라 동작이 달라지며, Vulkan도 부분적으로만 지원합니다.

참고로 모바일 쪽에는 이미 별도의 프레임 페이싱 계층이 문서화되어 있습니다. 공식 문서는 프레임 페이싱을 “애플리케이션이 기기의 네이티브 리프레시 레이트보다 낮은 프레임레이트로 렌더하도록 제한하는 시스템”으로 정의하고, 안드로이드에서는 Swappy(Android Game SDK)를 사용하며 r.setframepace로 목표를 지정할 수 있다고 안내합니다. 데스크톱·콘솔용 새 모드가 노리는 지점도 결국 같은 종류의 계층입니다.

9

VRR — 개발자가 할 일은 사실 없다

가변 리프레시 레이트는 브랜드가 아니라 표준의 일부가 됐습니다.

FreeSync, G-SYNC 등 여러 이름으로 불렸지만 지금은 HDMI 표준의 일부가 되어 브랜드 구분의 의미가 줄었습니다. HDMI Forum의 2.1 발표문은 향상된 리프레시 레이트 기능 첫 항목으로 “Variable Refresh Rate(VRR)는 지연 · 저더 · 프레임 테어링을 줄이거나 없애 더 유연하고 세밀한 게임플레이를 제공한다”를 들었습니다.

질문답
언제 도움이 되는가게임이 모니터의 리프레시 레이트를 따라가지 못할 때만 도움이 됩니다. 모니터가 다음 프레임 시작을 드랍하는 대신 지연시킬 수 있기 때문입니다. 다만 프레임을 붙잡아둘 수 있는 시간에는 한계가 있습니다.
게임에서 어떻게 활성화하는가하지 않습니다. VSync 유무와 무관하게 동작하며, 오롯이 유저가 켜고 끄는 것입니다. 개발자가 할 일은 프레임을 놓치지 않는 것뿐입니다.

기초 다지기

“붙잡아둘 수 있는 시간의 한계”가 무슨 뜻인가

VRR 디스플레이에는 동작 범위가 있습니다. AMD 문서는 “FreeSync 지원 그래픽 카드는 디스플레이가 보고한 능력에 따라 적절한 최대·최소 리프레시 레이트를 감지해 설정한다”고 설명합니다. 문제는 프레임레이트가 그 최소값 아래로 떨어질 때입니다. 모니터는 프레임을 무한정 붙잡고 있을 수 없습니다.

그래서 벤더들은 같은 대응을 합니다 — 프레임을 복제해 여러 번 표시하는 것입니다. AMD는 이를 LFC(Low Framerate Compensation)로 이름 붙이고 “60–144Hz 디스플레이라면 40FPS로 도는 게임의 프레임을 두 배로 늘려 80Hz로 동기화할 수 있다”는 예시까지 적어두었습니다. NVIDIA의 공식 안내 역시 “드라이버는 최소 리프레시 레이트 아래의 프레임을 배수로 늘린다”고 서술합니다.

즉 VRR 아래쪽 경계에서는 같은 프레임이 두 번 표시됩니다. 정수 배로 고르게 반복하고 리프레시 레이트도 그에 맞추므로 복제 자체가 3:2 풀다운 같은 저더를 만들지는 않습니다. 하지만 VRR은 10장에서 다루는 델타 타임 문제(히치 뒤 진행량이 요동치는 것)는 건드리지 않습니다. VRR이 “프레임 드랍을 막아주는 장치”이지 “부드러움을 보장하는 장치”가 아닌 이유입니다.

10

델타 타임과 저더 — 드랍 프레임 0이 부드러움을 뜻하지 않는 이유

이 세션에서 가장 값진 통찰입니다.

델타 타임의 성질

  • 델타 타임은 물리 · 시퀀스 · 애니메이션 등 게임 안의 모든 것을 얼마나 진행시킬지의 양입니다.
  • 현재 프레임의 델타 타임은 문자 그대로 “직전에 렌더된 프레임에 걸린 시간”입니다 → 항상 한 프레임 뒤처져 있습니다.
  • VSync를 놓치면 델타 타임이 증가합니다.

이건 좋은 일입니다. 게임이 게임 시간을 따라잡게 해주고, 오디오 같은 시간 기반 시스템과 어긋나는 것을 막아줍니다. 게임만 멈췄다 이어지는데 오디오는 계속 흘러가면 곤란합니다.

저더는 정확히 무엇이 어긋난 것인가

시뮬레이션한 델타 타임과, 그 프레임이 실제로 화면에 표시되는 간격이 어긋날 때 발생합니다. 이 어긋남은 다음 세 사실이 동시에 성립하기 때문에 생깁니다.

  1. 표시 프레임 사이의 간격은 (프레임을 놓치지 않는 한) 일정하다.
  2. 그런데 델타 타임 값은 히치 직후에 크게 요동친다.
  3. 애니메이션과 카메라는 델타 타임을 그대로 적용한다 → “얼마나 진행시켰는지”와 “실제로 몇 ms가 흘렀는지”가 불일치한다.

도해

히치를 흡수해도 델타 타임은 요동친다

델타 타임

히치

표시 간격

시간 →

정상 프레임히치 프레임재충전 중 짧은 프레임
아래 레인은 표시 간격이 처음부터 끝까지 일정합니다 — 드랍된 프레임이 0입니다. 위 레인은 같은 구간의 델타 타임이며, 히치에서 한 번 크게 튀고 그 뒤 파이프라인이 다시 채워지는 동안 여러 프레임이 평소보다 짧게 돕니다. 표시 간격은 일정한데 진행량은 들쭉날쭉한 이 상태가 곧 저더입니다.

기초 다지기

같은 현상을 디스플레이 쪽 문서가 부르는 이름

이 어긋남은 게임만의 문제가 아니라 영상 표시 전반의 고전적 문제이고, 그래서 정식 정의가 있습니다. 마이크로소프트의 디스플레이 하드웨어 가이드라인은 24fps 필름을 60Hz에 얹을 때를 이렇게 설명합니다 — “리프레시 레이트를 영상 프레임 레이트의 정수배로 맞추면 3:2 풀다운을 수행할 필요가 없어진다. 각 프레임이 정확히 41.7ms씩 표시된다. 60Hz에서 필요한 3:2 풀다운은 33.3ms, 50ms, 33.3ms, 50ms … 패턴을 만들어 필름 저더를 유발한다.”

구조가 정확히 같습니다. 프레임은 하나도 누락되지 않았고, 표시 간격도 규칙적인데, 각 프레임이 담고 있는 시간의 양이 다르다. 게임에서는 이 불일치를 만드는 것이 3:2 풀다운이 아니라 히치 직후의 델타 타임 요동이라는 차이뿐입니다.

히치의 해부 — 세 단계

① 프레임 드랍

게임이 프레임을 계산하는 동안 마지막 프레임이 화면에 더 오래 남습니다. 게임이 얼어붙는 구간입니다.

② 리커버리

다음 프레임이 큰 델타 타임을 적용해 시뮬레이션 시간을 빠르게 따라잡습니다. 게임이 순간이동하듯 확 나가는 느낌입니다.

③ 파이프라인 재충전

파이프라인이 다시 채워지면서 이후 프레임들이 평소보다 짧게 돕니다.

핵심 통찰 — 흡수해도 저더는 남는다

파이프라인이 드랍 프레임을 전부 흡수해서 드랍된 프레임이 0이어도, ② 리커버리와 ③ 재충전 때문에 델타 타임은 여전히 요동칩니다. 그리고 그 델타 타임이 애니메이션과 카메라에 그대로 적용됩니다. 프레임 그래프만 보고 안심하면 안 되는 이유입니다.

사진 · 자료

델타 타임의 요동을 실제로 읽는 화면

Unreal Insights의 프레임 트랙. Rendering Frames와 Game Frames 트랙에 프레임 번호와 함께 12.2ms, 14.4ms, 12.8ms 같은 프레임 시간이 나열되어 있다
Rendering Frames와 Game Frames 트랙에 프레임 번호와 프레임 시간이 함께 붙습니다 — 1688(12.2ms), 1689(14.4ms), 1690(12.8ms), 1691(12.6ms), 1692(11.9ms), 1693(13.4ms), 1694(11.5ms) … 드랍은 없지만 값이 매 프레임 흔들립니다. 이 흔들림이 그대로 애니메이션과 카메라에 들어갑니다.출처: Epic Games — Timing Insights 공식 문서 · © Epic Games, Inc.

회전 바퀴 실험 — 저더를 눈에 보이게 만들기

발표자는 이 주제를 다룬 다른 발표들의 화면에서 저더가 잘 보이지 않았다는 문제의식에서, 회전을 이용해 재현 가능한 실험을 만들었습니다.

항목설정
대상60등분된 바퀴를 초당 1회전 → 60fps에서 매 프레임 정확히 1/60 회전
기대 결과검은 선들이 항상 정지해 보여야 합니다. 흔들리면 그게 저더입니다. 전체 회전을 확인할 수 있도록 녹색 표식을 하나 추가했습니다.
주입한 히치파이프라인이 완전히 흡수할 수 있는 최대 크기의 인위적이지만 실제인 히치[9]
결과드랍된 프레임 0(실제 녹화, 조작 없음). 그런데도 바퀴가 눈에 띄게 흔들립니다.

극단적으로 과장된 예시이지만, 일반적인 프레임에서도 정확히 같은 현상이 일어나고 있습니다.

11

히치를 깔끔하게 흡수하려면

“델타 타임을 고정하면 되지 않나”는 답이 아닙니다.

고정 델타 타임이 답이 아닌 이유

일부 게임에서는 잘 작동하기도 하지만 권장되지 않습니다. 이유가 두 가지 있습니다.

#이유
1히치가 발생하면 오디오는 계속 재생되는데 게임은 사실상 얼어붙습니다 — 동기가 깨집니다.
2화면에 “정확히 초당 60프레임”이라는 건 없습니다. 실제로는 59.94Hz 같은 값이므로 게임 시간이 서서히 드리프트합니다.

기초 다지기

59.94Hz는 오차가 아니라 표준입니다

이 값은 측정 오차나 제조 편차가 아닙니다. 마이크로소프트의 인코더 API 문서는 일반적인 프레임 레이트 표에서 59.94를 60000/1001, 29.97을 30000/1001로 명시합니다. 같은 회사의 디스플레이 가이드라인도 “59.94 fps (NTSC TV)”를 60fps와 별개 항목으로 나열합니다.

차이는 1000분의 1 수준이지만 누적됩니다. 실제 리프레시가 59.94Hz인 화면에서 델타 타임을 1/60로 고정하면, 실제로 1시간이 흐르는 동안 프레임은 약 215,784장 표시되고 게임 시간은 약 3,596.4초만 진행됩니다 — 시간당 약 3.6초씩 실제 시간보다 뒤처집니다. 오디오나 네트워크처럼 실제 시간을 쓰는 시스템과 계속 벌어지는 것이고, “고정하면 깔끔하다”가 성립하지 않는 물리적 이유입니다.

세션이 제시한 임시 방편 — 델타 타임 보정 플러그인

엔진의 히치 내성에 대해 잔뜩 이야기해놓고 저더 문제에 해결책을 안 주는 건 좀 그렇더라고요.

— 발표자

대상은 r.GTSyncType 2를 쓰지 않는 게임, 즉 기본값 r.GTSyncType 0 환경입니다. 세 가지 모드가 소개됐습니다.[10]

모드동작
Hitch Absorb앞의 바퀴 실험처럼 저더를 완전히 제거합니다 — 흡수 가능한 범위 안에서.
Mitigation더 큰 히치용. 흡수할 수 있는 양에는 한계가 있으므로 고르게 펴서(even out) 완화합니다.
Hitch Pass어떻게 해도 안 되는 거대한 히치용. 감지해서 그대로 통과시키되 리커버리를 최대한 부드럽게 만듭니다.

발표자는 이것이 새 프레임 페이싱 모드가 나오기 전까지의 임시 방편이라고 명시했습니다.

Q&A

현장 질문에서 나온 보충

프레임 생성(Frame Generation)은 스왑 체인과 어떻게 상호작용하나

프레임젠은 기본적으로 스왑 체인을 가로챕니다(hijack). 더 이상 게임이 스왑 체인과 직접 상호작용하지 않고, 해당 프레임워크에 프레임을 넘기면 그 이후의 프레젠테이션은 그쪽이 처리합니다.

리워크된 CVar들이 블루프린트 노드로 노출되나

모든 CVar는 이미 블루프린트에서 변경할 수 있습니다. Execute Console Command 노드에 CVar 이름과 값을 넘기면 됩니다. 전용 노드가 있는 것은 아니지만 매우 간단하며, 어떤 CVar든 어떤 값으로든 설정할 수 있습니다.

60Hz / 120Hz / 144Hz, VRR 유무에 따라 권장이 달라지나

달라지지 않습니다. 콘솔은 r.GTSyncType 2, PC는 벤더 저지연 솔루션, 모바일은 r.GTSyncType 1이라는 권장은 어떤 리프레시 레이트를 노리든, VRR이 켜져 있든 아니든 동일하게 유효합니다.

다만 180Hz · 240Hz처럼 매우 높은 리프레시 레이트에서는 지연 자체가 워낙 낮아서, 오히려 히치 내성을 더 챙기는 쪽으로 갈 여유가 생깁니다 → r.GTSyncType 0으로 돌아가도 괜찮습니다.

게임 스레드가 잠든 원인을 구분할 수 있나

가능합니다. 앞의 설명을 다 제쳐두고 말하면 게임 스레드는 언제나 “이전 렌더 스레드”를 기다립니다. 기본값을 바꾸지 않는 한 그것이 항상 게임 스레드가 기다리는 대상입니다.

정리

이 세션에서 가져갈 것

모든 것이 하나의 트레이드오프로 수렴한다

입력 지연 ↔ 히치 내성. rhi.SyncSlackMS 하나가 이 둘 사이의 다이얼이고, 나머지 CVar들은 그 다이얼의 범위를 바꾸는 것입니다. 공식 문서가 이 CVar를 설명하며 “줄이면 지연 감소 · 히치에 취약, 늘리면 히치 내성 · 지연 증가”라고 쓴 것이 이 세션의 요약이기도 합니다.

플랫폼별로 답이 다르다 — 같은 설정을 전부에 밀어넣는 것이 최악이다

하드웨어가 고정된 콘솔은 고정 지연(r.GTSyncType 2)이, 하드웨어가 변하는 PC는 적응형(벤더 저지연 SDK)이 정답입니다. 모바일은 r.GTSyncType 1. 공식 문서도 r.GTSyncType 2를 “지원되는 플랫폼에서만”이라고 못 박고, 미지원 플랫폼에서는 모드 1로 폴백한다고 명시합니다.

“드랍 프레임 0”이 “부드럽다”를 뜻하지 않는다

파이프라인이 히치를 완벽히 흡수해도 리커버리와 재충전 때문에 델타 타임 요동은 남고, 그게 저더로 보입니다. 표시 간격은 일정한데 각 프레임의 진행량이 다르다는 구조는 24fps 필름을 60Hz에 얹을 때의 필름 저더와 정확히 같습니다. 프레임 그래프만 보고 안심하면 안 됩니다.

Exclusive Fullscreen은 유물이다

DX12 + Flip Model이면 보더리스로도 컴포지터를 건너뜁니다(Independent Flip). 벤더 문서 자체가 “애플리케이션이 정말 전체 화면 독점 모드를 필요로 하는지 재고할 만하다”고 권합니다. 해상도를 낮추고 싶다면 Secondary Upscale이 더 낫습니다 — UI가 네이티브 해상도로 남으니까요.

측정 구간을 먼저 합의하라

click-to-photon과 PC Latency는 다른 값이고, 언리얼 stat unit의 Input은 또 다른 구간(입력 샘플링 → flip)입니다. 어느 구간을 재는지 정하지 않으면 개선했는데도 숫자가 안 움직이는 일이 생깁니다. PresentMon은 이 구간들을 Click To Photon Latency · PC Latency · Display Latency로 나눠 정의해 두었습니다.

VRR은 프레임 드랍을 막아주지만 부드러움을 보장하지 않는다

개발자가 활성화할 것은 없고 할 일은 “프레임을 놓치지 않는 것”뿐입니다. 그리고 VRR 하단 경계에서는 LFC처럼 같은 프레임을 정수 배로 복제해 표시합니다. 복제 자체는 저더를 만들지 않지만, VRR이 델타 타임 문제까지 풀어 주지는 않습니다.

참고 자료

이 문서의 사실 확인에 사용한 자료

세션에서 직접 안내된 자료와, 이 문서를 정리하며 대조한 1차 자료를 함께 실었습니다.

세션에서 직접 안내한 원문

세션 원문 · 최우선

A Frame’s Life: Frame Timing, Synchronization, and Latency in UE

발표 내용 전체를 텍스트로 공개한 문서이며, 슬라이드에 없는 내용까지 포함되어 있고 계속 업데이트된다고 안내됐습니다. 새 프레임 페이싱 모드, 저더 완화 플러그인의 배포 위치, Insights flip 마커 CVar 등 이 문서가 각주로 남긴 항목들이 여기에서 확정됩니다.

토론

에픽 개발자 커뮤니티 포럼 스레드

위 원문에 대한 질의응답이 이어지는 곳입니다.

세션 정보

언리얼 페스트 서울 2026 — Day 1 Track 2 세션 페이지

다시보기와 자료는 에픽 라운지 리소스 페이지에 순차 공개됩니다.

에픽 공식 문서

Epic Games 공식 문서

Low Latency Frame Syncing in Unreal Engine

r.GTSyncType 0/1/2의 정의, r.OneFrameThreadLag의 역할, 병렬 렌더링 도입으로 Present 호출 주체가 RHI 스레드로 바뀐 경위, 30Hz·rhi.SyncInterval 2에서 최악 130ms, 그리고 rhi.SyncSlackMS 0 → 약 66ms / 10 → 약 76ms 예시의 출처입니다.

Epic Games 공식 문서

Unreal Engine Console Variables Reference

r.VSync · rhi.SyncInterval · rhi.SyncSlackMS · r.OneFrameThreadLag · r.GTSyncType · t.MaxFPS · r.ScreenPercentage · r.SecondaryScreenPercentage.GameViewport · r.Upscale.Quality의 설명문과 기본값을 이 문서의 표기 그대로 인용했습니다.

Epic Games 공식 문서

Screen Percentage with Temporal Upscale

Primary(시간적 또는 공간적) / Secondary(공간적) 업스케일의 정의, r.Upscale.Quality 0~5 목록, 그리고 “UI가 항상 네이티브 해상도로 렌더되도록 1080p나 4K에 두라”는 권장의 출처입니다.

Epic Games 공식 문서

Stat Commands · Unreal Insights Reference · Timing Insights

stat unit이 표시하는 Frame · Game · Draw · GPU · RHIT · DynRes의 정의, 그리고 Insights 트레이스 채널 목록(Frame / RHICommands / RenderCommands / RenderTrace 등)을 확인한 자료입니다.

Epic Games 공식 문서

Frame Pacing for Mobile Devices

“프레임 페이싱은 애플리케이션이 기기의 네이티브 리프레시 레이트보다 낮은 프레임레이트로 렌더하도록 제한하는 시스템”이라는 정의와 r.setframepace, Swappy 사용의 출처입니다.

플랫폼 · 벤더 1차 자료

Microsoft Learn

For best performance, use DXGI flip model

플립 모델의 버퍼 개수 하한(2), DirectFlip과 Independent Flip이 데스크톱 합성을 우회한다는 서술, MPO가 전용 하드웨어 스캔아웃 플레인을 예약한다는 설명, 그리고 전체 화면 독점 모드를 재고하라는 권고의 출처입니다.

Microsoft Learn

Variable refresh rate displays · Multiplane overlay support

DXGI_PRESENT_ALLOW_TEARING을 sync interval 0에서 쓰라는 권장(보더리스 풀스크린 포함), 그리고 MPO가 Windows 8.1(WDDM 1.3)에 도입된 정식 기능이라는 확인입니다.

Microsoft Learn

Display — component guidelines · AVEncVideoOutputFrameRate

3:2 풀다운이 33.3ms / 50ms 패턴을 만들어 필름 저더를 유발한다는 저더의 정의, 그리고 59.94 = 60000/1001이라는 NTSC 유래 리프레시 레이트 확인의 출처입니다.

Intel

PresentMon

Present Mode 값(Hardware: Legacy Flip / Hardware: Independent Flip / Hardware Composed: Independent Flip / Composed: Flip 등)의 정의와, PC Latency · Display Latency · Click To Photon Latency · Animation Error 지표 정의의 출처입니다.

NVIDIA · AMD · Intel

Reflex · Anti-Lag 2 · Xe Low Latency 개발자 문서

PC Latency와 엔드투엔드 시스템 지연의 구분, Reflex가 CPU·GPU 파이프라인을 동기화한다는 설명, Anti-Lag 2가 입력 샘플링 직전에 지연을 삽입한다는 설명(gpuopen.com/anti-lag-2/), XeLL이 다음 프레임 시작 전 CPU 지연을 계산한다는 설명(intel.com XeLL 개발자 가이드)의 출처입니다.

HDMI Forum · AMD · NVIDIA

HDMI 2.1 VRR 발표문 · FreeSync · G-SYNC 지원 문서

VRR이 HDMI 2.1 사양의 일부라는 확인(hdmiforum.org 2017-11 발표문), FreeSync가 디스플레이가 보고한 최소·최대 범위를 따른다는 서술과 LFC의 프레임 복제 정의, 그리고 “드라이버는 최소 리프레시 레이트 아래의 프레임을 배수로 늘린다”는 NVIDIA 지원 문서의 출처입니다.

부록

각주

세션에서 언급됐지만 공개된 공식 자료로 확정하지 못한 항목을 따로 모았습니다.

각주

  1. Insights의 flip 마커를 켜는 콘솔 변수 — 이 문서는 변수 이름을 특정하지 않았습니다. 에픽 공식 콘솔 변수 레퍼런스와 Unreal Insights 레퍼런스의 트레이스 채널 목록에는 flip · present · vsync를 이름에 가진 항목이 없습니다. 프레임 경계를 다루는 공식 채널은 Frame(“Game and Rendering frames.”)이며, 발표에서 언급된 전용 트랙도 정리 시점 기준으로 공식 문서에 없습니다. 정확한 변수 이름은 세션 원문 아티클에서 확인하는 것이 확실합니다. ↩
  2. MPO가 주 모니터에서만 활성화된다는 제약 — 마이크로소프트의 WDDM MPO 문서와 DXGI 문서, 그리고 NVIDIA의 공개 문서 어디에서도 “MPO가 주 디스플레이를 요구한다”는 서술을 확인하지 못했습니다. MPO 자체가 정식 기능이라는 점과, DWM이 전용 하드웨어 스캔아웃 플레인을 예약할 수 있다는 점은 공식 문서로 확인됩니다. 주 모니터 조건은 드라이버 구현이나 특정 세대의 동작일 가능성이 있으므로, 실제 환경에서는 PresentMon의 Present Mode 값으로 직접 확인하는 것이 안전합니다. ↩
  3. 게임 스레드가 “2프레임 전”을 기다린다는 서술 — 공식 레퍼런스는 r.GTSyncType 0을 “게임 스레드를 렌더 스레드에 동기화(기본값)”로만 정의하고, 두 프레임 전을 함께 확인한다는 서술은 담고 있지 않습니다. 게임 스레드와 렌더 스레드가 함께 앞서나가는 것을 막기 위한 두 번째 체크라는 설명은 발표 내용이며, 공식 문서로는 “게임 스레드가 렌더링 스레드보다 온전한 한 프레임 이상 앞서가지 않도록 보장한다”까지만 확인됩니다. ↩
  4. Oversubscription — 이 이름으로 동작을 설명하는 에픽 공식 문서를 확인하지 못했습니다. 대기 중인 워커 스레드를 대신할 스레드를 추가로 띄운다는 개념 자체는 태스크 그래프의 일반적인 기법이지만, CVar 이름이나 API 표기는 확정하지 않았습니다. ↩
  5. stat unit의 Input 줄 — 공식 Stat Commands 문서는 stat unit이 표시하는 항목을 Frame · Game · Draw · GPU · RHIT · DynRes로만 명시하며, Input 줄은 문서화되어 있지 않습니다. “입력 샘플링 시점부터 백 버퍼가 프론트로 flip될 때까지”라는 측정 구간의 정의는 발표 내용이며, 이 문서는 그대로 옮기되 공식 문서에 없는 항목임을 밝혀둡니다. ↩
  6. “파이프라인 6프레임” 기준의 200 / 100 / 50ms — 파이프라인 깊이 6프레임이라는 숫자와 그로부터 계산한 프레임레이트별 지연은 공식 문서에서 확인되지 않습니다. 공식 문서가 제시하는 숫자는 다른 조건의 것입니다 — rhi.SyncInterval 2인 30Hz 타이틀의 최악 130ms, 그리고 r.GTSyncType 2 + rhi.SyncSlackMS 0에서의 최선 약 66ms. 파이프라인 깊이는 버퍼 개수와 CVar 설정에 따라 달라지므로, 위 표는 “깊이가 고정일 때 프레임 시간이 줄면 총 지연도 줄어든다”는 관계를 보여주는 예시로 읽는 것이 정확합니다. ↩
  7. rhi.SyncSlackMS의 기본값 — 발표에서는 10ms로 안내됐습니다. 공식 문서에서 확인되는 것은 “0에서 한 프레임 분량까지 설정할 수 있다”는 범위와, 이 값을 10으로 올렸을 때 최선 지연이 약 66ms에서 약 76ms로 늘어난다는 예시입니다. 기본값 자체는 엔진 버전과 플랫폼 설정에 따라 달라질 수 있으므로 프로젝트에서 직접 확인하는 편이 안전합니다. ↩
  8. 새 프레임 페이싱 모드의 버전— 어느 버전에 들어갈지는 발표 시점에 확정되지 않았고, 공식 릴리스 노트에서도 해당 모드를 확인하지 못했습니다. Q&A에서는 특정 마이너 버전에 넣고 싶었으나 확실하지 않으며 다음 메이저의 main 브랜치에 올라갈 가능성이 크고 1년 이내를 희망하지만 약속할 수는 없다는 답변이 있었습니다. ↩
  9. 회전 바퀴 실험에 주입한 히치 크기 — 이 문서는 “파이프라인이 완전히 흡수할 수 있는 최대 크기”라는 성질만 적었습니다. 발표에서 언급된 구체적인 밀리초 값은 특정 빌드와 특정 설정에서의 실측치이며, 공식 문서로 검증할 수 있는 수치가 아닙니다. 세션 원문 아티클에는 약 90ms 수준의 값이 제시되어 있습니다. ↩
  10. 델타 타임 보정 플러그인의 이름과 모드 명칭 — 세 모드의 명칭(Hitch Absorb / Mitigation / Hitch Pass)은 발표에서 소개된 표기이며 공식 엔진 문서에서 확인되지 않습니다. 세션 원문 아티클에서는 DeltaTimeSmoother라는 이름의 플러그인과 그 배포 위치를 안내하고 있으므로, 실제로 도입할 때는 원문의 링크와 문서를 기준으로 삼는 것이 정확합니다. ↩