사전 수직 동기화
개념

수직 동기화

gabury1고친 사람 github-actions[bot]

수직 동기화는 새 그림을 화면에 내보내는 때를 모니터의 박자에 맞춥니다. 모니터가 한 화면을 다 칠하고 맨 위로 돌아가는 순간에만 그림을 바꿔 끼웁니다. 그래서 한 화면에 두 장면이 섞여 찢어져 보이는 일이 없어집니다. 대신 먼저 준비된 그림도 그 순간까지 기다려야 합니다.

쉽고 빠른 이해

수직 동기화는 그림을 바꿔 끼우는 순간을 모니터가 화면을 새로 칠하기 시작하는 순간에 맞춥니다. 1초에 화면을 60번 새로 칠하는 모니터라면, 그림은 모니터가 새로 칠하기 시작하는 1초 60번의 순간에만 바뀝니다.

이게 없으면 모니터가 화면을 칠하는 도중에 그림이 바뀝니다. 그러면 위쪽은 앞 장면, 아래쪽은 새 장면인 화면이 보입니다. 가로로 찢어진 선이 생기는 것입니다.

어떻게 도는가:

  1. 프로그램이 보이지 않는 판에 다음 장면을 다 그립니다
  2. 모니터가 마지막 줄까지 칠하고 맨 위로 돌아갈 때까지 기다립니다
  3. 그 틈에 보이는 판과 그리는 판을 맞바꿉니다

대가는 기다림입니다. 버튼을 누른 결과가 화면에 늦게 뜹니다. 한 장을 그리는 데 조금만 늦어도 다음 순간까지 한 번 더 기다리므로 움직임이 끊겨 보이기도 합니다.

그래서 영상 재생이나 화면 스크롤처럼 찢김이 거슬리는 곳에서는 켭니다. 반응이 급한 게임에서는 끄거나, 모니터가 그림이 올 때마다 새로 칠하는 가변 주사율을 씁니다.

상세

연극에서 장면을 바꿀 때는 막을 내리고 세트를 옮깁니다. 막이 오른 채로 세트를 옮기면 관객은 반쯤 바뀐 무대를 봅니다. 그래서 무대 담당은 다음 세트를 일찍 준비해 두어도 막이 내려올 때까지 기다립니다.

수직 동기화도 같은 일을 합니다. 그래픽 프로그램이 새 그림을 화면에 내보내는 순간을 모니터가 화면을 새로 칠하기 시작하는 순간에 맞춥니다. 영어로는 vertical synchronization 입니다. 줄여서 VSync 라고 부릅니다. 이 절은 모니터가 화면을 칠하는 방식에서 출발해 무엇이 어긋나는지, 수직 동기화가 그것을 어떻게 막는지, 그 대가가 무엇인지를 차례로 봅니다.

모니터가 화면을 칠하는 방식

모니터는 화면을 한 번에 바꾸지 않습니다. 맨 윗줄부터 한 줄씩, 한 줄 안에서는 왼쪽에서 오른쪽으로 픽셀 색을 새로 칠합니다. 마지막 줄까지 칠하면 맨 위로 돌아가 다시 시작합니다.

모니터가 1초에 화면 전체를 몇 번 새로 칠하는지를 주사율(refresh rate)이라고 합니다. 단위는 Hz(hertz, 헤르츠)입니다. 60 Hz 모니터는 1초에 60번 칠하므로 한 번 칠하는 데 60분의 1초가 걸립니다.

모니터가 칠할 색은 프레임버퍼라는 메모리에서 옵니다. 프레임버퍼는 화면의 픽셀마다 색 값을 하나씩 담아 둔 메모리입니다. 프로그램이 그린 그림은 이 메모리에 놓여야 화면에 나올 수 있습니다.

프레임버퍼를 모니터까지 옮기는 일은 따로 맡은 장치가 합니다. 이 메모리를 한 줄씩 읽어 모니터로 보내는 장치가 디스플레이 컨트롤러입니다. 이렇게 읽어 내보내는 일을 스캔아웃(scanout)이라고 부릅니다. 모니터가 윗줄부터 칠하는 것은 스캔아웃이 윗줄부터 읽어 보내기 때문입니다.

마지막 줄을 다 칠하고 맨 윗줄을 다시 시작하기 전에는 짧은 틈이 있습니다. 이 틈 동안에는 아무 줄도 칠하지 않습니다. 이 틈을 수직 귀선 구간(vertical blanking interval)이라고 부릅니다. 수직 동기화가 노리는 순간이 이 틈입니다.

「수직」이라는 이름은 이 되돌아감에서 왔습니다. 한 줄 안에서 옆으로 가는 것이 수평 방향입니다. 마지막 줄에서 맨 위로 올라가는 것이 수직 방향입니다. 모니터로 가는 신호에는 원래 「이제 맨 위로 돌아가라」는 표시가 들어 있습니다. 이것을 수직 동기 신호라고 부릅니다.

수직 동기 신호는 수직 귀선 구간 안에서 나옵니다. 그래서 이 신호를 보면 지금이 아무 줄도 칠하지 않는 틈이라는 것을 알 수 있습니다. 그래픽 프로그램에서 말하는 수직 동기화는 그림을 바꿔 끼우는 순간을 이 신호가 알리는 틈, 곧 수직 귀선 구간에 맞추는 일입니다.

화면 찢김

그리는 쪽과 칠하는 쪽은 서로 다른 박자로 돕니다. 프로그램은 한 장면을 다 그리는 대로 내보냅니다. 모니터는 자기 주사율대로 칠합니다. 두 박자가 맞으라는 법이 없습니다.

모니터가 화면 중간쯤을 칠하고 있을 때 프레임버퍼의 내용이 새 장면으로 바뀌었다고 해 봅시다. 이미 칠한 위쪽 줄에는 앞 장면이 남아 있습니다. 아직 안 칠한 아래쪽 줄은 새 장면으로 칠해집니다. 한 화면에 두 장면이 위아래로 붙어 보이게 됩니다.

block-beta
columns 1
  a["① 위쪽 줄들 · 먼저 칠했다 · 앞 장면"]
  b["② 찢긴 선 · 칠하기가 위에서 여기까지 내려왔을 때 프레임버퍼가 바뀌었다"]
  c["③ 아래쪽 줄들 · 나중에 칠한다 · 새 장면"]

움직이는 물체가 있으면 두 장면의 위치가 달라서 경계에서 가로로 어긋난 선이 보입니다. 이 결함을 화면 찢김(tearing)이라고 부릅니다. 화면이 빠르게 옆으로 움직일수록 잘 보입니다.

백엔드에서도 같은 모양의 문제가 있습니다. 32비트 기계에서 64비트 정수를 32비트씩 두 번에 나눠 읽는다고 해 봅시다. 그 도중에 다른 스레드가 그 값을 고치면, 앞 절반은 옛값이고 뒤 절반은 새값인 수를 읽게 됩니다. 화면 찢김은 모니터가 프레임버퍼를 읽는 도중에 프로그램이 그것을 고쳐서 생기는 같은 종류의 어긋남입니다.

더블 버퍼링과 스왑

화면 찢김을 막으려면 먼저 모니터가 읽는 판과 프로그램이 그리는 판을 떼어야 합니다. 그래서 프레임버퍼를 두 장 둡니다. 모니터가 읽는 장을 프런트 버퍼, 프로그램이 그리는 장을 백 버퍼라고 부릅니다. 이렇게 두 장을 쓰는 방식이 더블 버퍼링입니다.

프로그램은 백 버퍼에 한 장면을 끝까지 그립니다. 다 그리면 두 버퍼의 역할을 맞바꿉니다. 이것을 스왑이라고 부릅니다. 메모리 내용을 복사하지 않고 「이제 이쪽을 읽어라」라는 가리킴만 바꾸므로 순식간에 끝납니다.

더블 버퍼링만으로는 찢김이 다 사라지지 않습니다. 스왑이 스캔아웃 도중에 일어나면 모니터는 위쪽을 옛 버퍼에서, 아래쪽을 새 버퍼에서 읽습니다. 판을 떼어 놓았어도 바꿔 끼우는 순간이 나쁘면 똑같이 찢어집니다.

스왑을 수직 귀선 구간에 맞춘다

수직 동기화는 바로 이 순간을 고릅니다. 프로그램이 스왑을 요청해도 곧바로 바꾸지 않고 다음 수직 귀선 구간까지 미룹니다. 그 틈에는 모니터가 아무 줄도 칠하지 않으므로, 다음 번 칠하기는 처음부터 끝까지 한 장면만 읽습니다.

sequenceDiagram
    participant 프로그램
    participant 컨트롤러 as 디스플레이 컨트롤러
    participant 모니터
    프로그램->>프로그램: 백 버퍼에 다음 장면을 다 그린다
    프로그램->>컨트롤러: 스왑을 요청한다
    Note over 프로그램: 스왑이 끝날 때까지 멈춘다
    컨트롤러->>모니터: 마지막 줄까지 앞 장면을 보낸다
    Note over 컨트롤러,모니터: 수직 귀선 구간 · 이 틈에 스왑한다
    컨트롤러->>모니터: 맨 윗줄부터 새 장면을 보낸다
    컨트롤러-->>프로그램: 스왑이 끝났다
    프로그램->>프로그램: 다음 장면을 그리기 시작한다

그림에서 프로그램은 스왑을 요청한 뒤 멈춰 있습니다. 두 판 모두 손댈 수 없기 때문입니다. 프런트 버퍼는 모니터가 지금 읽고 있습니다. 백 버퍼에는 다음에 화면으로 나갈 장면이 기다리고 있어서 덮어쓸 수 없습니다. 스왑이 끝나야 옛 프런트 버퍼가 새 백 버퍼가 되어 다음 장면을 받습니다.

이 멈춤은 프로그램 코드에서 스왑 함수 한 줄로 보입니다. 게임이나 그래픽 프로그램은 대개 「입력 읽기 → 상태 갱신 → 그리기 → 스왑」을 되풀이하는 게임 루프로 돕니다. 아래는 그 루프를 줄여 적은 꼴입니다.

C
while (running) {
    update();      // 상태 갱신
    draw(back);    // 백 버퍼에 그림
    swap();        // 귀선 구간까지 멈춤
}

swap() 은 백엔드 코드의 블로킹 호출과 비슷하게 동작합니다. 다음 수직 귀선 구간이 올 때까지 돌아오지 않습니다. 그 결과 루프 한 바퀴가 모니터의 한 번 칠하는 시간과 같아집니다. 프로그램이 저절로 주사율에 발을 맞추는 것입니다.

기다림이 만드는 대가

첫째 대가는 반응이 늦어진다는 것입니다. 장면을 일찍 다 그려도 다음 수직 귀선 구간까지 화면에 안 나옵니다. 버튼을 누른 뒤 그 결과가 화면에 보이기까지의 시간을 입력 지연(input lag)이라고 합니다. 기다린 만큼 이 시간이 늘어납니다.

둘째 대가는 프레임률이 계단처럼 떨어진다는 것입니다. 프레임률은 화면이 1초에 새 그림으로 몇 번 바뀌는지를 세는 값입니다. 수직 동기화를 켜면 스왑은 수직 귀선 구간에서만 일어나므로, 한 장에 걸린 시간이 60분의 1초를 조금만 넘겨도 한 번을 건너뛰고 다음 틈까지 기다립니다.

아래 표는 60 Hz 모니터에서 한 장을 그리는 시간에 따라 화면에 나오는 간격이 어떻게 달라지는지 보입니다. 시간은 모니터가 화면을 한 번 칠하는 시간, 곧 60분의 1초를 단위로 셉니다. 스왑이 끝나자마자 다음 장을 그리기 시작한다고 셈했습니다.

한 장 그리는 시간 화면에 나오는 간격 1초에 바뀌는 횟수
한 번 칠하는 시간보다 짧다 한 번 칠하는 시간 60
한 번보다 길고 두 번보다 짧다 두 번 칠하는 시간 30
두 번보다 길고 세 번보다 짧다 세 번 칠하는 시간 20

둘째 줄을 보면 한 장이 60분의 1초를 아주 조금만 넘겨도 1초에 바뀌는 횟수가 60에서 30으로 반이 됩니다. 60 · 30 · 20 처럼 60을 나눈 값으로만 움직이는 셈입니다.

표는 한 장 그리는 시간이 늘 같다고 셈했습니다. 실제로는 장면마다 그리는 시간이 조금씩 다릅니다. 그리는 시간이 한 번 칠하는 시간 언저리를 오가면, 어떤 장은 한 번 칠하는 시간 만에, 어떤 장은 두 번 칠하는 시간 만에 화면에 나옵니다.

화면에 나오는 간격이 이렇게 고르지 않으면 움직임이 툭툭 끊겨 보입니다. 이 끊김을 스터터링(stuttering)이라고 부릅니다.

기다림을 줄이는 방법

수직 동기화의 대가를 덜어 내는 방법은 여럿입니다. 세 가지를 찢김과 기다림 두 축으로 견주면 이렇습니다.

방법 하는 일 화면 찢김 기다림
수직 동기화를 끈다 다 그리는 대로 곧바로 스왑한다 생긴다 없다
트리플 버퍼링 백 버퍼를 하나 더 두어 스왑을 기다리는 동안에도 다음 장을 그린다 안 생긴다 줄어든다
가변 주사율 모니터가 그림이 도착할 때마다 새로 칠한다 안 생긴다 모니터가 칠하는 속도를 늦추거나 당길 수 있는 폭 안에서는 거의 없다

첫째 줄은 찢김을 받아들이고 반응 속도를 얻습니다. 끄면 프레임률이 주사율을 넘을 수도 있습니다. 넘는 몫은 온전한 한 장으로 화면에 나오지 못합니다. 모니터가 한 번 칠하는 동안 스왑이 여러 번 일어나, 여러 장이 위에서부터 조각조각 섞여 들어가고 찢긴 선도 여럿 생깁니다.

트리플 버퍼링은 판을 한 장 더 쓰는 대신 그리기가 멈추지 않게 합니다. 가변 주사율은 방향을 뒤집어 모니터가 프로그램을 기다립니다. 모니터가 그 기능을 갖춰야 쓸 수 있습니다.

켜는 경우와 끄는 경우

고르는 기준은 찢김과 지연 가운데 무엇이 더 거슬리느냐입니다. 수직 동기화를 켜고 끄는 것만으로는 두 가지를 동시에 없앨 수 없습니다. 수직 동기화는 그 사이에서 찢김 쪽을 막는 선택입니다. 둘을 함께 줄이려면 앞 절의 가변 주사율처럼 모니터가 따로 갖춘 기능이 있어야 합니다.

조건 흔한 선택
영상 재생 · 화면 스크롤 · 천천히 움직이는 장면 켠다. 찢긴 선이 눈에 띄고 몇 밀리초 늦는 것은 티가 안 난다
반응 속도가 조작감을 좌우하는 게임 끄거나 가변 주사율을 쓴다. 몇 밀리초의 입력 지연이 조작감에 드러난다
그리는 시간이 한 번 칠하는 시간 언저리를 오가는 장면 켜면 60과 30 사이를 오가며 끊긴다. 트리플 버퍼링이나 가변 주사율을 함께 쓴다
화면에 안 보이는 계산 · 서버에서 이미지를 만드는 일 해당 없다. 칠할 모니터가 없다

관련 항목

수직 동기화가 박자를 맞추는 모니터 쪽 장치와 신호

모니터 · 주사율 · 스캔아웃 · 수직 귀선 구간 · 수평 동기화 · 디스플레이 컨트롤러 · CRT

수직 동기화가 스왑 순간을 정하는 버퍼

프레임버퍼 · 색 버퍼 · 프런트 버퍼 · 백 버퍼 · 더블 버퍼링 · 버퍼 스왑 · 스왑 체인

수직 동기화를 켜고 끌 때 오가는 화면 결함

티어링 · 화면 찢김 · 입력 지연 · 스터터링 · 프레임 드롭

수직 동기화의 기다림을 줄이는 다른 수단

트리플 버퍼링 · 가변 주사율 · Adaptive-Sync · FreeSync · G-SYNC

수직 동기화에 박자를 맞추는 반복과 지표

프레임률 · 프레임 시간 · 프레임 예산 · 게임 루프 · requestAnimationFrame · 블로킹 호출

수직 동기화를 켜고 끄는 그래픽 API

그래픽 API · OpenGL · Vulkan · Direct3D

수직 동기화가 속하는 상위 분류

그래픽스 · 렌더링 · 렌더 파이프라인 · GPU

다른 이름: VSync · V-Sync · vertical synchronization · vertical sync · 수직동기화 · 브이싱크