← SEARCHABLE ARCHIVE
DirectX 12 · 2026-07-11

DX12 Command Queue와 Fence

CPU와 GPU를 정확한 시점에 동기화하기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

DirectX 12에서는 CPU가 Command List를 기록하고 GPU가 나중에 실행한다. 두 장치가 동시에 움직이므로 아직 GPU가 사용하는 자원을 CPU가 덮어쓰지 않게 동기화해야 한다.

Command Queue와 Command List

Command List는 그리기 명령을 기록하는 목록이고, Command Queue는 완성된 목록을 GPU에 제출한다.

CPU
CommandAllocator 초기화
CommandList 기록
CommandList Close
        ↓
CommandQueue 제출
        ↓
GPU 실행

CommandAllocator는 기록에 필요한 메모리를 관리한다. GPU가 이전 프레임의 명령을 아직 읽고 있는데 같은 Allocator를 Reset하면 안 된다.

Fence로 완료 시점을 확인한다

void WaitForGPU()
{
    const UINT64 SignalValue = ++FenceValue;
    CommandQueue->Signal(Fence, SignalValue);

    if (Fence->GetCompletedValue() < SignalValue)
    {
        Fence->SetEventOnCompletion(SignalValue, FenceEvent);
        WaitForSingleObject(FenceEvent, INFINITE);
    }
}

Queue에 Signal 값을 넣으면 GPU가 앞선 명령을 모두 처리한 뒤 Fence 값을 갱신한다. CPU는 완료 값이 목표보다 작을 때만 Event를 기다린다.

매 프레임 전부 기다리면 느리다

삼각형 예제에서는 이해하기 쉽게 Present 뒤마다 WaitForGPU()를 호출할 수 있다. 하지만 이렇게 하면 CPU와 GPU가 겹쳐 일하지 못하고 매 프레임 직렬로 실행된다.

실전에서는 BackBuffer 수만큼 Frame Resource를 두고 각 프레임의 Fence 값을 따로 기록한다.

struct FrameContext
{
    ComPtr<ID3D12CommandAllocator> Allocator;
    UINT64 FenceValue = 0;
};

현재 사용할 프레임 자원의 Fence만 기다리고, 다른 프레임은 GPU가 계속 처리하게 만든다.

Debug Layer를 켠다

DX12는 잘못된 상태 전환이나 Descriptor 사용을 개발자가 직접 책임진다. 초기화 전에 Debug Layer를 켜면 어떤 Resource State와 명령 순서가 잘못됐는지 훨씬 빨리 찾을 수 있다.

DX12 동기화의 목적은 무조건 기다리는 것이 아니다. CPU와 GPU가 동시에 최대한 일하게 두면서, 같은 자원을 다시 사용할 때만 정확하게 기다리는 것이다.