멀티코어 커맨드 분배의 개념과 구조적 역할
그래픽 렌더링의 본질은 GPU에 명령(Command)을 전달하는 것이다. 하지만 단일 스레드가 모든 드로우콜(Draw Call)을 관리하면, CPU의 병목이 발생한다. 현대 엔진은 이를 해소하기 위해 멀티코어 커맨드 분배(Multicore Command Distribution) 구조를 도입한다. 각 CPU 코어가 독립된 커맨드 리스트(Command List)를 생성하고, 이를 병합(Merge)해 GPU 커맨드 큐에 제출하는 방식이다.
이 과정의 핵심은 커맨드 생성과 병합의 병렬성이다. 예를 들어 8코어 시스템에서는 6개 코어가 드로우 콜을 생성하고, 나머지 2개 코어가 GPU 제출을 담당한다. 이렇게 하면 CPU 준비 단계가 GPU 처리 속도와 거의 동기화된다.
또한 커맨드 분배 구조는 “Render Context” 단위로 관리된다. 각 스레드는 고유한 렌더 컨텍스트를 가지며, 해당 컨텍스트 내에서만 리소스를 참조한다. 이로써 GPU 리소스 접근 충돌이 발생하지 않는다. 컨텍스트별로 커맨드 버퍼를 분리하면, 프레임당 드로우 콜 수가 30~40% 증가하더라도 안정적인 프레임 유지가 가능하다.
GPU 커맨드 큐 최적화와 병목 해소 전략
GPU 커맨드 큐(Command Queue)는 CPU가 제출한 명령을 순서대로 실행하는 파이프라인이다. 대부분의 그래픽 API(Vulkan, DirectX 12, Metal)는 세 가지 큐를 제공한다.
① Graphics Queue: 렌더링 명령을 실행한다.
② Compute Queue: 병렬 연산(예: 물리, AI, 포스트 프로세싱)에 사용된다.
③ Transfer Queue: 리소스 전송과 복사를 담당한다.
커맨드 큐 최적화의 핵심은 CPU-GPU 간 비동기성 유지다. CPU가 너무 빨리 커맨드를 제출하면 GPU가 과부하되고, 너무 늦으면 GPU가 유휴 상태(idle)가 된다. 이를 방지하기 위해 엔진은 Frame Pacing Controller를 사용한다. 컨트롤러는 CPU 커맨드 제출 속도를 GPU의 실제 소비율에 맞춰 동적으로 조정한다.
또한 커맨드 버퍼의 크기도 중요하다. 너무 작으면 API 호출 오버헤드가 증가하고, 너무 크면 캐시 효율이 떨어진다. 대부분의 엔진은 2~4MB 단위의 버퍼를 사용하며, 버퍼 풀(Buffer Pool)을 운영해 재사용한다. 이러한 풀링 구조는 CPU 메모리 단편화를 최소화하고, GPU로의 전송 효율을 높인다.
병렬 커맨드 생성 과정에서는 스레드 간 리소스 접근을 엄격히 제어해야 한다. 텍스처나 버퍼를 여러 스레드가 동시에 수정하면 GPU 충돌이 발생한다. 이를 막기 위해 Resource Barrier가 사용된다. 바리어는 리소스 상태를 “Read”, “Write”, “Transition”으로 정의하고, 상태가 충돌하지 않도록 자동 변환한다. 이러한 바리어 최적화만으로도 GPU 스톨(stall) 발생률을 20~30% 줄일 수 있다.
병렬 렌더링 동기화와 프레임 일관성 유지
멀티코어 커맨드 구조가 완벽하게 작동하려면 GPU 동기화가 반드시 필요하다. 각 커맨드 큐가 독립적으로 실행되지만, 최종 프레임 출력은 반드시 하나의 타임라인에 맞춰야 하기 때문이다. 이를 위해 엔진은 Fence Synchronization과 Timeline Semaphore를 함께 사용한다.
Fence는 CPU와 GPU 간의 신호 장치다. CPU는 Fence 신호를 전송한 뒤 GPU가 해당 작업을 완료할 때까지 대기한다. 하지만 과도한 Fence 사용은 지연을 유발하므로, Timeline Semaphore로 대체한다. 세마포어는 GPU 내부에서 단계별 완료 신호를 관리하므로, CPU 개입이 최소화된다.
동기화 단계에서 또 하나 중요한 개념이 Frame Index Loop이다. 일반적으로 엔진은 2~3프레임을 동시에 처리하는 “Triple Buffering” 구조를 사용한다. CPU는 N+1 프레임을 준비하고, GPU는 N 프레임을 렌더링한다. 이 두 프로세스가 서로 간섭하지 않도록, 프레임 인덱스로 구분된 커맨드 버퍼가 사용된다. 이를 통해 GPU가 지연 없이 연속적인 프레임을 생성할 수 있다.
렌더링 동기화의 마지막 단계는 “Presentation Barrier”다. 모든 프레임은 출력 전에 GPU 파이프라인의 완전한 종료를 확인해야 한다. 이를 보장하지 않으면 화면 깜빡임이나 Tear 현상이 발생한다. 엔진은 Swap Chain을 제어하면서 Present 명령을 전송하기 전 GPU 큐의 상태를 검사한다. GPU가 완료 신호를 반환하기 전에는 다음 프레임을 제출하지 않는다. 이러한 제어는 CPU가 아닌 GPU 내부에서 이루어지기 때문에 프레임 타임 안정성이 향상된다.