사전 스왑체인
개념

스왑체인

gabury1고친 사람 github-actions[bot]

스왑체인은 프로그램이 그린 그림을 모니터에 차례로 넘겨 줍니다. 그림을 담는 판을 여러 장 두고 프로그램과 모니터가 돌려 가며 씁니다. 그래서 모니터는 다 그린 그림만 보여 줍니다. 프로그램은 그동안 다음 그림을 그립니다. 그래픽 프로그램이 화면에 그림을 띄우는 마지막 관문입니다.

쉽고 빠른 이해

스왑체인은 화면에 띄울 그림판 몇 장을 돌려 쓰는 묶음입니다. 판이 세 장이면 한 장은 화면에 떠 있습니다. 한 장은 프로그램이 그리는 중입니다. 남은 한 장은 차례를 기다립니다.

이게 없으면 프로그램이 그림을 그리는 도중에 모니터가 그 판을 읽어 갑니다. 그러면 화면 위쪽에는 새 그림이, 아래쪽에는 옛 그림이 뜹니다.

어떻게 도나:

  1. 프로그램이 빈 판 한 장을 받아 옵니다
  2. 그 판에 이번 화면을 다 그립니다
  3. 다 그린 판을 모니터 쪽에 넘기고 다음 판을 받으러 갑니다

대가는 기다림입니다. 판이 화면에 뜰 차례를 기다리는 동안 키보드를 누른 결과가 늦게 보입니다. 판마다 화면 크기만 한 메모리도 듭니다.

화면에 띄우지 않는 그리기에는 스왑체인이 필요 없습니다. 그린 그림을 파일로 저장하거나 그래픽 카드로 계산만 할 때가 그렇습니다.

상세

이 절은 먼저 그림을 한 장에만 그리면 무엇이 깨지는지 봅니다. 그다음 스왑체인이 판 여러 장을 어떻게 돌리는지, 판을 언제 화면에 넘기는지를 봅니다.

예는 Vulkan 으로 듭니다. Vulkan 은 프로그램이 스왑체인을 직접 만들고 다루게 하는 그래픽 API(Application Programming Interface, 응용 프로그래밍 인터페이스)입니다. 끝에서 다른 API 가 같은 일을 어떻게 보이는지와 스왑체인의 대가를 봅니다.

강의실의 미닫이 칠판

어느 강의실에 미닫이 칠판이 석 장 있습니다. 앞에 나온 칠판 한 장을 학생들이 보는 동안 강사는 뒤에 숨은 칠판에 다음 내용을 씁니다.

다 쓰면 칠판을 밀어 바꿉니다. 학생들은 강사가 쓰다 만 칠판을 한 번도 보지 않습니다. 셋째 칠판은 지울 차례를 기다리거나 미리 써 둔 내용을 품고 기다립니다.

칠판은 그림판입니다. 학생들은 모니터입니다. 칠판 석 장과 미는 순서를 한데 묶은 것이 스왑체인입니다.

그리다 만 그림이 보이는 문제

GPU(Graphics Processing Unit, 그래픽 처리 장치)는 그래픽 카드의 계산 칩입니다. 프로그램이 내린 그리기 명령을 받아 화면 한 장을 그립니다.

GPU 가 그린 그림은 메모리 위의 판에 담깁니다. 프레임버퍼는 화면의 점마다 색을 적어 둔 이 메모리입니다. 모니터는 이 판을 보고 화면에 무엇을 띄울지 정합니다.

모니터는 1초에 수십 번 프레임버퍼를 맨 윗줄부터 차례로 읽어 화면에 뿌립니다. 이렇게 읽어 가는 일을 스캔아웃이라고 합니다.

프레임버퍼가 한 장뿐이면 GPU 가 그리는 동안에도 모니터가 읽어 갑니다. 위쪽은 새 그림을 읽고 아래쪽은 아직 안 고친 옛 그림을 읽습니다. 화면이 가로로 찢어져 보이는 이 현상이 티어링입니다.

그래서 판을 두 장 이상 둡니다. 모니터가 읽는 판은 프런트 버퍼입니다. 지금 화면에 보이는 그림이 이 판에 있습니다.

프로그램이 그리는 판은 백 버퍼입니다. 화면 뒤에 숨어 있어서 그리다 만 모습이 보이지 않습니다.

백 버퍼를 다 그리면 두 판의 역할을 맞바꿉니다. 이 맞바꿈이 스왑입니다. 스왑체인이라는 이름도 여기서 왔습니다.

판 여러 장을 돌려 쓰는 묶음

스왑체인은 이런 판 여러 장과 그 판을 넘겨주는 순서를 함께 관리합니다. 스왑체인에서는 판 한 장을 이미지라고 부릅니다. 이미지는 그래픽 카드 메모리에 잡아 둔, 화면 한 장 크기의 판입니다.

앞에서 본 프레임버퍼·프런트 버퍼·백 버퍼도 스왑체인에서는 모두 이 이미지입니다. 그 순간 모니터가 읽는 이미지가 프런트 버퍼이고, 프로그램이 그리는 이미지가 백 버퍼입니다.

반대편에는 프레젠테이션 엔진이 있습니다. 프레젠테이션 엔진은 운영체제와 드라이버 쪽에서 이미지를 받아 모니터로 내보내는 부분입니다. 프로그램이 모니터를 직접 건드리지 않도록 이 부분이 사이에 섭니다.

다 그린 이미지를 이 엔진에 넘기는 일을 프레젠트라고 부릅니다. 넘긴 이미지를 언제 화면에 띄울지는 엔진이 정합니다.

스왑체인의 이미지는 어느 순간이든 프로그램과 엔진 가운데 한쪽 것입니다. 프로그램은 엔진에게 이미지를 하나 받아 와야 그릴 수 있습니다. 프레젠트하고 나면 그 이미지는 다시 엔진 것이 됩니다.

이미지 한 장이 한 바퀴 도는 길을 그리면 이렇습니다. 엔진 것인 동안 이미지는 화면 차례를 기다리거나, 화면에 떠 있거나, 비어 있습니다.

stateDiagram-v2
    state "엔진 것 · 비어 있음" as 빈판
    state "프로그램 것 · 그리는 중" as 그림
    state "엔진 것 · 화면 차례 대기" as 대기
    state "엔진 것 · 화면에 떠 있음" as 화면
    [*] --> 빈판: 스왑체인을 만든다
    빈판 --> 그림: 받아 와서 그린다
    그림 --> 대기: 프레젠트한다
    대기 --> 화면: 모니터가 읽을 차례가 온다
    화면 --> 빈판: 다음 이미지가 화면을 넘겨받는다

한 이미지가 화면에 떠 있는 동안에는 프로그램이 그 이미지에 그릴 수 없습니다. 모니터가 아직 읽고 있기 때문입니다. 다음 이미지가 화면에 뜨고 나서야 그 이미지는 다시 빈 판이 됩니다.

스왑체인이 붙는 창

스왑체인이 이미지를 내보내는 곳은 화면 전체가 아니라 창 하나입니다. 그래서 스왑체인은 만들 때 창 하나에 묶입니다.

Vulkan 은 그 창을 서피스라는 객체로 감싸서 받습니다. 서피스는 운영체제의 창을 Vulkan 이 다룰 수 있는 모양으로 감싼 것입니다. 프로그램은 서피스를 먼저 만들고, 그 서피스를 넘겨 스왑체인을 만듭니다.

창을 다루는 방식은 운영체제마다 다릅니다. 그래서 Vulkan 은 서피스와 스왑체인을 본체에 넣지 않고 WSI(Window System Integration, 창 시스템 연동)라는 확장으로 따로 둡니다.

확장은 Vulkan 본체 밖에 덧붙여 필요할 때만 켜는 기능 묶음입니다. 화면에 띄우지 않고 계산만 하는 프로그램은 이 확장을 켜지 않아도 됩니다.

한 프레임의 세 단계

화면 한 장을 프레임이라고 부릅니다. 프로그램은 프레임마다 받아 오기, 그리기, 프레젠트를 되풀이합니다.

받아 오기는 이미지 번호를 먼저 줍니다. 그 번호의 이미지가 실제로 비는 것은 조금 뒤일 수 있습니다. 모니터가 아직 그 이미지를 읽고 있을 수 있기 때문입니다.

그래서 세 단계 사이에 기다림이 끼어 있습니다. GPU 는 받아 온 이미지가 실제로 비기 전에 그리면 안 됩니다. 엔진은 그리기가 끝나기 전에 화면에 내면 안 됩니다.

이 순서는 세마포어로 맞춥니다. 세마포어는 GPU 안의 한 일이 끝났다고 다른 일에 알리는 신호입니다.

sequenceDiagram
    participant 앱 as 프로그램
    participant GPU
    participant 엔진 as 프레젠테이션 엔진
    앱->>엔진: 빈 이미지를 달라
    엔진-->>앱: 이미지 번호를 준다
    Note over 엔진: 이미지가 실제로 비면 신호 하나를 켠다
    앱->>GPU: 그 이미지에 그려라
    Note over GPU: 신호를 기다렸다가 그린다
    Note over GPU: 다 그리면 신호 하나를 켠다
    앱->>엔진: 그 이미지를 화면에 내라
    Note over 엔진: 신호를 기다렸다가 차례에 넣는다

그림에서 프로그램은 그리기와 프레젠트를 요청만 하고 바로 다음 일로 갑니다. 받아 오기도 번호만 받으면 바로 돌아옵니다. 프로그램이 멈춰 기다리는 때는 내줄 번호가 하나도 없을 때뿐입니다.

나머지 기다림은 GPU 와 엔진 사이에서 신호로 처리됩니다. 덕분에 CPU(Central Processing Unit, 중앙 처리 장치)는 GPU 가 그리는 동안 다음 프레임을 준비할 수 있습니다.

프로그램이 받아 오는 이미지는 차례대로 오지 않을 수 있습니다. 어느 이미지를 줄지는 엔진이 정합니다. 그래서 프로그램은 받은 번호를 보고 그 이미지에 그립니다.

프레임 루프 코드 한 토막

위 세 단계를 Vulkan 의 C 코드로 줄여 적으면 이렇습니다. 그리기 명령을 담고 GPU 에 넘기는 부분은 draw 라는 가상의 함수 하나로 줄였습니다.

C
uint32_t i;
vkAcquireNextImageKHR(dev, sc,
  UINT64_MAX, imgReady,
  VK_NULL_HANDLE, &i); // i = 2

draw(images[i], imgReady, drawn);

VkPresentInfoKHR p = {
  .sType =
    VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
  .waitSemaphoreCount = 1,
  .pWaitSemaphores = &drawn,
  .swapchainCount = 1,
  .pSwapchains = &sc,
  .pImageIndices = &i, // 2번을 낸다
};
vkQueuePresentKHR(queue, &p);

vkAcquireNextImageKHR 는 이미지를 받아 오는 함수입니다. 받은 이미지의 번호를 i 에 적어 줍니다. 세 장짜리 스왑체인이면 0, 1, 2 가운데 하나입니다. 위 주석은 그중 2가 온 경우입니다.

imgReady 와 drawn 은 세마포어입니다. imgReady 는 이미지가 실제로 비었다는 신호입니다. drawn 은 그리기가 끝났다는 신호입니다. vkQueuePresentKHR 은 drawn 이 켜지기를 기다렸다가 2번 이미지를 화면 차례에 넣습니다.

코드에 나온 나머지 이름은 이런 뜻입니다.

이름 뜻
dev 프로그램이 열어 둔 그래픽 카드(디바이스)
sc 스왑체인
UINT64_MAX 기다릴 수 있는 가장 긴 시간. 내줄 이미지가 없으면 생길 때까지 기다리라는 뜻이다
VK_NULL_HANDLE 안 쓰는 칸을 비워 두는 값. 여기서는 펜스 자리를 비웠다
.sType 이 구조체가 어떤 종류인지 적는 칸
queue GPU 에 일을 넘기는 줄인 큐. 프레젠트 요청도 이 줄로 넘긴다

함수 이름 끝의 KHR(Khronos)은 확장으로 더한 기능이라는 표시입니다. 앞에서 본 WSI 의 함수들이 이런 확장입니다.

판을 몇 장 두나

스왑체인을 만들 때 이미지를 몇 장 둘지 정합니다. 장 수에 따라 얻는 것과 잃는 것이 갈립니다.

장 수 흔히 부르는 이름 얻는 것 잃는 것
2 더블 버퍼링 메모리가 적게 들고 지연이 짧다 한 장이 화면에 떠 있는 동안 프로그램이 남은 한 장을 다 그리면 멈춰 기다린다
3 트리플 버퍼링 화면 차례를 기다리는 동안에도 셋째 장에 그릴 수 있다 메모리가 한 장 더 들고 그린 장면이 화면에 뜨기까지 더 오래 걸릴 수 있다

장 수를 아무렇게나 고를 수는 없습니다. 창과 그래픽 카드가 허용하는 최소 장 수와 최대 장 수가 있습니다. 프로그램은 서피스에 그 범위를 물어본 뒤 그 안에서 고릅니다.

판을 언제 넘기나

다 그린 이미지를 아무 때나 화면에 바꿔 끼우면 티어링이 다시 생깁니다. 모니터가 화면 중간을 읽는 도중에 판이 바뀌기 때문입니다.

모니터는 한 장을 다 읽고 맨 위로 돌아가기 전에 짧게 쉽니다. 이 틈이 vblank(vertical blank, 수직 귀선 구간)입니다.

이 틈에 맞춰 판을 바꾸면 찢김이 없습니다. 이렇게 맞추는 일이 수직 동기화입니다.

Vulkan 은 판을 언제 바꿀지를 프레젠트 모드로 고르게 합니다. 흔히 쓰는 모드는 넷입니다. 이름에 든 FIFO 는 First In First Out(먼저 들어온 것이 먼저 나간다)의 줄임말입니다.

모드 판을 바꾸는 때 티어링
IMMEDIATE 프레젠트하는 즉시 바꾼다 생길 수 있다
FIFO 넘겨받은 순서대로 줄을 세우고 vblank 마다 한 장씩 꺼낸다 없다
FIFO_RELAXED 대개 FIFO 처럼 한다. 프레젠트가 늦어 vblank 를 이미 놓쳤으면 기다리지 않고 바로 바꾼다 늦은 경우에만 생길 수 있다
MAILBOX vblank 를 기다린다. 기다리는 칸이 하나뿐이라 새 이미지가 오면 기다리던 이미지를 버리고 새것으로 바꾼다 없다

네 모드 가운데 어느 그래픽 카드에서나 반드시 지원되는 모드는 FIFO 하나뿐입니다. 그래서 다른 모드를 쓰려면 먼저 지원되는지 물어봅니다.

FIFO 와 MAILBOX 는 둘 다 찢김이 없지만 밀릴 때 하는 일이 다릅니다. FIFO 의 줄에 설 수 있는 이미지는 스왑체인의 장 수를 넘지 못합니다. 모든 이미지가 줄에 서거나 화면에 떠 있으면 받아 올 빈 이미지가 없습니다. 그래서 FIFO 는 줄이 차면 프로그램이 받아 오기에서 멈춰 기다립니다.

MAILBOX 는 프로그램을 멈추지 않고 화면에 못 나간 이미지를 버립니다. 그래서 MAILBOX 는 가장 최근에 그린 장면을 내보내는 대신 그린 일 일부가 헛수고가 됩니다.

창 크기가 바뀔 때

스왑체인의 이미지는 만들 때의 창 크기에 맞춰 만들어집니다. 사용자가 창을 늘리거나 줄이면 이미지 크기가 창과 어긋납니다.

그러면 받아 오기나 프레젠트가 「이 스왑체인은 낡았다」는 결과를 돌려줍니다. Vulkan 에서는 VK_ERROR_OUT_OF_DATE_KHR 입니다. 아직 쓸 수는 있지만 창과 꼭 맞지는 않을 때는 VK_SUBOPTIMAL_KHR 을 돌려줍니다.

이 결과를 받으면 프로그램은 흔히 GPU 가 하던 일을 마치기를 기다렸다가 새 크기로 스왑체인을 다시 만듭니다. 새로 만들 때 옛 스왑체인을 넘겨주면 엔진이 이미 화면 차례에 들어간 이미지를 이어서 처리할 수 있습니다.

창을 최소화하면 크기가 0이 됩니다. 크기가 0이면 스왑체인을 다시 만들 수 없습니다. 그래서 창이 돌아올 때까지 기다립니다.

다른 API 에서 보이는 모습

스왑체인은 Vulkan 만의 것이 아닙니다. 화면에 그림을 내는 그래픽 API 는 모두 이 구조를 씁니다. API 마다 다른 것은 프로그램에게 얼마나 드러내느냐입니다.

그래픽 API 스왑체인이 보이는 모습
Vulkan 프로그램이 장 수·크기·프레젠트 모드를 골라 직접 만든다
Direct3D DXGI(DirectX Graphics Infrastructure) 가 스왑체인 객체를 준다. 프로그램은 다 그린 뒤 Present 를 부른다
OpenGL 창 시스템이 숨긴다. 프로그램은 다 그린 뒤 버퍼를 맞바꾸는 함수 하나만 부른다
Metal 화면에 붙은 레이어가 프레임마다 그릴 이미지를 하나씩 내준다
WebGPU 캔버스가 프레임마다 그릴 이미지를 내주고 브라우저가 화면에 낸다

OpenGL 에서 프로그램이 부르는 맞바꿈 함수는 창 시스템마다 이름이 조금씩 다릅니다. eglSwapBuffers · glXSwapBuffers · SwapBuffers 처럼 앞머리가 붙거나 빠집니다. 모두 SwapBuffers(버퍼 맞바꾸기)로 끝납니다.

하는 일은 같습니다. 백 버퍼를 프레젠트합니다. 그다음 백 버퍼를 받아 옵니다.

스왑체인이 치르게 하는 대가

첫째 대가는 지연입니다. 그린 이미지는 화면 차례를 기다리는 줄에 섭니다. 줄이 길수록 마우스를 움직인 결과가 늦게 화면에 뜹니다. 이 늦음을 입력 지연이라고 부릅니다.

둘째 대가는 메모리입니다. 이미지 한 장이 화면 크기만 합니다. 장 수가 늘면 그만큼 그래픽 카드 메모리를 더 씁니다.

셋째 대가는 관리할 일입니다. Vulkan 처럼 스왑체인을 드러내는 API 에서는 프로그램이 할 일이 셋입니다. 장 수와 모드를 고릅니다. 세마포어로 순서를 맞춥니다. 창이 바뀌면 스왑체인을 다시 만듭니다.

이 가운데 하나를 빠뜨리면 오류 없이 화면이 멈추거나 깨질 수 있습니다.

관련 항목

스왑체인이 해결하는 화면 문제

티어링 · 스터터 · 입력 지연 · 프레임 페이싱 · 프레임 드롭

스왑체인을 이루는 구성 요소

이미지 · 프레임버퍼 · 프런트 버퍼 · 백 버퍼 · 색 버퍼 · 렌더 타깃 · 이미지 뷰 · 서피스 · 픽셀 포맷 · sRGB 프레임버퍼

스왑체인이 거치는 처리 단계

이미지 획득 · 프레젠트 · 버퍼 스왑 · 스캔아웃 · 프레젠트 모드 · 이미지 레이아웃 · 레이아웃 전환

스왑체인을 넘기는 때를 정하는 화면 타이밍

vblank · 수직 동기화 · 주사율 · 가변 주사율 · Adaptive-Sync · FreeSync · G-Sync

스왑체인 이미지의 순서를 맞추는 동기화 수단

세마포어 · 펜스 · 타임라인 세마포어 · 파이프라인 배리어 · 동기화 · 커맨드 버퍼 · 큐

스왑체인의 장 수를 부르는 방식

더블 버퍼링 · 트리플 버퍼링 · 싱글 버퍼링 · 메일박스 · FIFO

스왑체인을 제공하는 그래픽 API와 창 연동 계층

Vulkan · WSI · Direct3D · Direct3D 11 · Direct3D 12 · DXGI · 플립 모델 · OpenGL · EGL · Metal · CAMetalLayer · WebGPU · API

스왑체인이 속하는 상위 분류

그래픽스 · GPU · 렌더링 · 그래픽스 파이프라인 · 디스플레이 엔진 · 운영체제 · 디바이스 드라이버

다른 이름: swapchain · swap chain · 스왑 체인 · VkSwapchainKHR