https://github.com/eazuooz/YamYam_Engine/commit/7ac7ef2842181f6eb9f79e8afb22d3878c06cd32

DX12 Frame Loop & Fence 동기화 완전 해설

YamYam Engine 기준으로 작성된 DX12 프레임 루프와 GPU 동기화 아키텍처 문서. Editor 빌드(#define WITH_EDITOR)와 Game 빌드의 구조적 차이, 그리고 Fence가 왜 필요한지를 처음부터 순서대로 설명한다.


1. 들어가며 — GPU는 CPU의 명령을 즉시 실행하지 않는다

DirectX 12를 처음 접할 때 가장 큰 충격은 이것이다.

mCommandList->DrawIndexedInstanced(...); // 이 줄은 GPU가 즉시 실행하지 않는다

이 코드는 GPU에게 "지금 그려라"를 명령하는 게 아니다. CommandList라는 녹화 테이프에 그 명령을 기록하는 것이다. GPU가 실제로 그리기 시작하는 시점은 나중에 ExecuteCommandLists()를 호출할 때다.

CPU 시점:                          GPU 시점:

DrawIndexedInstanced() 호출        ← 아직 아무 것도 안 함
ExecuteCommandLists() 호출         ← 이제 GPU가 실행 시작
CPU는 계속 다음 작업 진행          ← GPU는 비동기로 처리 중

여기서 문제가 생긴다. CPU는 빠르게 다음 프레임을 준비하려 하는데, GPU는 아직 이전 프레임을 처리하고 있다. 이 타이밍을 맞추는 장치가 바로 Fence다.


2. Fence란 무엇인가

Fence는 CPU와 GPU 사이에 놓인 단방향 신호 깃발이다.

DirectX 12에서 **Fence(펜스)**는 CPU와 GPU 사이의 '교통 정리'를 담당하는 동기화 객체입니다. 작업을 기다려야 하는 핵심 이유는 CPU와 GPU가 서로 다른 속도로 독립적으로 작동하기 때문입니다.

  1. 데이터 무결성 유지 (자원 경합 방지)
  2. 비동기 실행 구조

image.png

   CPU                               GPU
    |                                 |
    |  mCommandQueue->Signal(fence, N)|
    |-------------------------------->|  "N번 깃발 꽂아줘"
    |                                 |
    |  (CPU는 계속 다른 일 함)         |  ... GPU 작업 중 ...
    |                                 |
    |                                 |  [GPU가 N까지 처리 완료]
    |                                 |  fence->GetCompletedValue() == N
    |                                 |
    |  (CPU가 N 이상인지 확인 가능)    |

Signal(fence, N)은 GPU에게 "네가 여기까지 실행을 완료하면, fence의 값을 N으로 만들어줘" 라고 예약하는 것이다. CPU는 나중에 fence->GetCompletedValue()로 GPU가 어디까지 왔는지 언제든지 폴링할 수 있다.

2.1 fence 값은 단조 증가(Monotonic)해야 한다