본문으로 건너뛰기
뒤로가기

[Vulkan] 커맨드 풀 설계: 스왑체인 기반과 프레임 기반 구조 비교 분석

업데이트:

이 글은 Vulkan을 학습하는 개발자의 관점에서, 커맨드 풀 설계를 위한 두 가지 접근 방식(스왑체인 이미지 기반과 프레임 인 플라이트 기반)을 비교하고 정리한 기록입니다.

[Vulkan] 커맨드 풀 설계: 스왑체인 기반과 프레임 기반 구조 비교 분석

Vulkan 튜토리얼을 따라 애플리케이션을 만들다 보면 자연스럽게 특정 구조를 사용하게 됩니다. 저 또한 스왑체인 이미지 개수에 맞추어 커맨드 버퍼를 생성하는 일반적인 방식으로 학습을 시작했습니다. 하지만 이 설계가 더 복잡한 상황에서는 비효율적이라는 점을 배우게 되어, 이를 개선하는 프레임 인 플라이트(Frame-in-Flight) 기반 설계와 비교하며 학습한 내용을 정리하고자 합니다.

2026-07-19 정정: 커맨드 풀, 커맨드 버퍼, submit fence, imageAvailable은 frame slot 기준으로 관리할 수 있지만, present가 기다리는 renderFinished는 swapchain image 기준으로 관리해야 안전합니다. 자세한 이유는 renderFinished 세마포어는 왜 프레임이 아니라 이미지에 묶어야 하는가에서 설명합니다.

1. 일반적인 학습 구조: 스왑체인 이미지 기반

대부분의 튜토리얼은 아래와 같은 리소스 구성을 기반으로 렌더링 파이프라인을 구축합니다.

항목개수설명
스왑체인 이미지3개화면에 표시하기 위한 이미지 버퍼
프레임 인 플라이트 (FIF)2개CPU가 GPU의 작업 완료를 기다리지 않고 미리 준비할 수 있는 프레임의 최대 개수
그래픽스 큐 패밀리1개모든 렌더링 명령을 제출하는 통로
커맨드 풀1개그래픽스 큐 패밀리에 종속된 커맨드 버퍼 할당자
커맨드 버퍼3개스왑체인 이미지 개수와 동일하게 생성

이 구조에서는 vkAcquireNextImageKHR을 통해 얻은 스왑체인 이미지의 인덱스를 사용하여, 해당 인덱스에 맞는 커맨드 버퍼에 렌더링 명령을 기록합니다.

2. 스왑체인 이미지 기반 구조의 특징

장점

단점

3. 대안 구조: 프레임 인 플라이트 기반

커맨드 기록과 submit 완료를 frame slot 단위로 재사용하기 위해, 각 프레임 인 플라이트(Frame-in-Flight)가 커맨드 풀, 커맨드 버퍼, fence와 imageAvailable을 소유하는 구조를 적용할 수 있습니다. 반면 present가 기다리는 renderFinished는 swapchain image 단위로 분리합니다.

struct FrameResources {
    VkCommandPool    commandPool;
    VkCommandBuffer  commandBuffer;
    VkFence          renderFence;
    VkSemaphore      imageAvailable;
};

// 커맨드 기록과 submit 완료에 묶이는 리소스는 frame slot별로 생성
FrameResources frames[MAX_FRAMES_IN_FLIGHT]; // 보통 2개

// present가 사용하는 세마포어는 swapchain image별로 생성
std::vector<VkSemaphore> renderFinishedByImage(swapchainImageCount);

이 구조에서는 스왑체인 이미지 인덱스를 커맨드 버퍼 선택에 사용하지 않습니다. 현재 CPU가 작업할 프레임(currentFrame)의 FrameResources를 사용하고, renderFinished를 선택할 때만 acquire가 반환한 imageIndex를 사용합니다.

4. 프레임 인 플라이트 기반 구조의 장점

5. 동기화 과정의 이해

프레임 인 플라이트 기반 구조의 주된 동기화 흐름은 다음과 같습니다.

  1. vkWaitForFences(): 현재 frame slot을 사용했던 이전 submit이 끝났는지 확인합니다.
  2. vkAcquireNextImageKHR(): imageAvailable[currentFrame]과 함께 렌더링할 imageIndex를 얻습니다.
  3. vkResetCommandPool(): fence로 안전을 확인한 frame slot의 커맨드 풀을 리셋하고 새로운 명령을 기록합니다.
  4. vkQueueSubmit(): imageAvailable[currentFrame]을 기다리고 renderFinishedByImage[imageIndex]를 signal하며, submit 완료를 renderFence로 추적합니다.
  5. vkQueuePresentKHR(): 같은 renderFinishedByImage[imageIndex]를 기다려 해당 이미지를 present합니다.

MAX_FRAMES_IN_FLIGHT = 2, 스왑체인 이미지 개수 = 3 구성은 흔히 사용하는 예시입니다. 중요한 것은 모든 리소스를 한 인덱스에 맞추는 것이 아니라, 각 리소스를 실제 재사용 보장에 맞는 기준으로 관리하는 것입니다.

6. 정리하며

스왑체인 이미지 기반 구조는 Vulkan의 기본 개념을 익히는 데 좋은 시작점입니다. 프레임 인 플라이트 기반 커맨드 기록은 CPU 작업 단위와 submit 완료 추적을 명확하게 만드는 유용한 대안이지만, 모든 리소스가 frame slot 소유가 되는 것은 아닙니다. frame slot과 swapchain image의 서로 다른 생명 주기를 구분하는 것이 동기화 설계의 핵심입니다.


이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.


공유하기:

이전 글
[WinAPI] 땅따먹기: 경로 병합과 점령 영역 선택
다음 글
[Godot] Terrain TileSet: Constraint로 이해하는 Autotiling