사전 프레임 예산
개념

프레임 예산

gabury1고친 사람 github-actions[bot]

프레임 예산은 화면 한 장을 그리는 일에 시간 한도를 정해 줍니다. 이 한도는 1초에 몇 장을 보여 줄지에서 거꾸로 셈해 나옵니다. 한 장에 필요한 계산과 그리기는 전부 이 시간 안에 끝나야 합니다. 넘기면 그 장이 늦게 나와 화면이 끊겨 보입니다.

쉽고 빠른 이해

프레임 예산은 화면 한 장을 만드는 데 쓸 수 있는 시간입니다. 1초에 60장을 보여 주려면 한 장에 쓸 수 있는 시간은 60분의 1초입니다.

이 한도가 있어야 「이만하면 되나」에 답할 수 있습니다. 한 장에 할 일마다 시간을 얼마씩 쓸지 나눌 수 있습니다. 새 기능이 그 시간을 얼마나 먹는지도 잴 수 있습니다.

어떻게 도는가:

  1. 1초에 몇 장을 보여 줄지 정합니다
  2. 1초를 그 장수로 나눠 한 장의 한도를 얻습니다
  3. 매 장의 일이 그 한도 안에 끝나도록 일을 덜거나 나눕니다

대가는 한 장에 넣을 수 있는 일의 상한입니다. 장수를 늘리면 한도가 줄어듭니다. 한 장이라도 한도를 넘기면 그 순간 화면이 멈칫합니다.

이 한도는 스크롤과 애니메이션과 게임처럼 화면이 움직이는 동안에 걸립니다. 멈춘 화면에서 버튼을 눌러 결과를 기다리는 일은 이 한도로 재지 않습니다.

상세

월급날까지 쓸 돈이 30만 원 남았다고 해 봅시다. 밥값과 교통비와 커피값을 모두 그 안에서 나눠야 합니다. 한 곳에 많이 쓰면 다른 곳을 줄여야 합니다.

화면이 움직여 보이는 것은 멈춘 그림을 잇달아 갈아 끼우기 때문입니다. 이 멈춘 그림 한 장이 프레임입니다. 이 문서가 「한 장」이라고 말하는 것이 프레임입니다.

1초에 프레임이 몇 장 바뀌는지는 프레임률로 셉니다. 단위는 FPS(frames per second, 초당 프레임 수)입니다. 60 FPS 면 1초에 그림을 60장 갈아 끼웁니다.

목표 프레임률을 정하면 한 장에 쓸 수 있는 시간이 따라 정해집니다. 1초를 목표 장수로 나누면 됩니다. 이 시간이 프레임 예산(frame budget)입니다. 60 FPS 가 목표면 한 장의 예산은 60분의 1초, 약 16.7밀리초입니다.

한 장을 내보내려면 세 가지 일을 해야 합니다. 사용자 입력을 읽습니다. 화면 속 상태를 계산합니다. 그림을 그립니다.

이 세 가지 일이 매 장 예산 안에 끝나야 합니다. 앞의 30만 원이 여기서는 16.7밀리초입니다. 그래서 예산은 한 장의 일을 여러 몫으로 나눠 쓰는 기준이 됩니다.

예산이 없으면 얼마나 줄여야 하는지 기준이 서지 않습니다. 예산이 서면 일마다 몇 밀리초를 써도 되는지 정할 수 있습니다. 그림자 효과 하나를 새로 넣을 때도 먼저 그 효과가 예산을 몇 밀리초 먹는지 잽니다. 그 값을 보고 들일지 고릅니다.

백엔드 개발자에게는 지연 예산(latency budget)과 같은 생각입니다. 응답 시간 목표를 정해 두고 그 안에서 데이터베이스 조회와 외부 호출에 시간을 나눠 주는 방식입니다. 프레임 예산은 이 한도가 1초에 수십 번씩 돌아옵니다. 한 장이 끝나면 곧바로 다음 장의 예산이 시작됩니다.

아래에서는 먼저 목표 프레임률마다 예산이 얼마인지 셈해 봅니다. 그다음 한 장의 일을 두 장치가 어떻게 나눠 맡는지, 예산을 넘긴 장이 화면에 어떻게 보이는지 봅니다. 끝으로 예산에 맞추는 방법과 게임 밖에서 이 예산을 쓰는 곳을 봅니다.

목표 프레임률과 예산

예산은 1초, 곧 1000밀리초를 목표 프레임률로 나눈 값입니다. 아래 표는 흔히 쓰는 목표마다 한 장의 예산을 적은 것입니다.

목표 프레임률 한 장의 예산 (밀리초) 흔히 쓰는 곳
30 FPS 33.3 그릴 것이 많은 장면의 게임
60 FPS 16.7 대부분의 게임과 화면 애니메이션
90 FPS 11.1 가상 현실 헤드셋
120 FPS 8.3 화면을 자주 새로 칠하는 휴대폰과 모니터
144 FPS 6.9 게임용 모니터

목표와 예산은 반비례합니다. 목표를 60 에서 120 으로 두 배 올리면 예산은 16.7밀리초에서 8.3밀리초로 반이 됩니다. 한 장에 넣을 수 있는 일도 절반으로 줄어듭니다.

예산과 프레임 시간

예산은 한도입니다. 한 장에 걸린 시간을 잰 값은 프레임 시간(frame time)입니다. 둘은 단위가 같아서 바로 견줄 수 있습니다.

예산이 16.7밀리초일 때 프레임 시간이 12밀리초면 4.7밀리초가 남습니다. 프레임 시간이 20밀리초면 3.3밀리초를 넘긴 것입니다. 성능을 다룰 때는 프레임 시간을 재서 예산과 견주는 일을 되풀이합니다.

한 장의 일을 나눠 맡는 CPU 와 GPU

한 장을 만드는 일은 두 장치가 나눠 합니다. 두 장치가 어떻게 겹쳐 일하는지 알면 예산이 어디에 걸리는지도 보입니다.

CPU(Central Processing Unit, 중앙 처리 장치)는 입력을 읽고 게임 상태를 계산합니다. 그다음 무엇을 그릴지 목록을 만들어 넘깁니다.

GPU(Graphics Processing Unit, 그래픽 처리 장치)는 그 목록을 받아 화면의 점 하나하나에 색을 칠합니다. 이 점 하나가 픽셀입니다.

두 장치는 동시에 일합니다. GPU 가 1번 장을 칠하는 동안 CPU 는 2번 장을 계산합니다. 아래 그림은 60 FPS 목표에서 두 장치가 장을 주고받는 순서입니다. 16.7밀리초 한 칸마다 두 장치가 서로 다른 장을 맡습니다.

sequenceDiagram
    participant CPU
    participant GPU
    Note over CPU: 1번 장 계산 (16.7밀리초 안)
    CPU->>GPU: 1번 장 그리기 목록
    Note over CPU,GPU: 다음 16.7밀리초 · CPU 는 2번 장 계산 · GPU 는 1번 장 칠하기
    CPU->>GPU: 2번 장 그리기 목록
    Note over CPU,GPU: 다음 16.7밀리초 · CPU 는 3번 장 계산 · GPU 는 2번 장 칠하기

앞에서 한 장의 일이 매 장 예산 안에 끝나야 한다고 했습니다. 두 장치가 겹쳐 일하므로 이 말은 한 장치가 한 장에 쓰는 시간을 두고 한 것입니다. CPU 의 몫도, GPU 의 몫도 각각 예산 안에 들어와야 합니다. 둘을 더한 값까지 예산 안에 들 필요는 없습니다.

어느 쪽이 예산을 넘기는지에 따라 덜어 낼 일도 달라집니다. GPU 쪽이 넘기는 상태가 GPU 바운드입니다. 이때는 칠할 픽셀 수나 그림의 세밀함을 줄여야 시간이 줄어듭니다.

CPU 쪽이 넘기는 상태는 CPU 바운드입니다. 이때는 계산할 일이나 GPU 에 넘길 그리기 목록을 줄여야 합니다. 칠할 픽셀 수를 줄여도 CPU 쪽 시간은 거의 줄지 않습니다.

예산을 넘긴 장

한 장이 예산을 넘기면 화면에 무엇이 보일까요. 먼저 화면을 보여 주는 장치의 박자부터 봅니다.

모니터는 1초에 정해진 횟수만큼 화면을 위에서 아래로 새로 칠합니다. 이 횟수가 주사율(refresh rate)입니다. 주사율이 높을수록 화면이 더 자주 바뀌어 움직임이 부드럽게 보입니다.

주사율은 Hz(hertz, 헤르츠)로 셉니다. 1초에 한 번이 1 Hz 입니다. 60 Hz 모니터는 16.7밀리초마다 화면을 한 번 새로 칠합니다.

목표 프레임률은 흔히 이 주사율에 맞춥니다. 그래서 60 Hz 모니터라면 한 장의 예산과 모니터의 박자가 같은 16.7밀리초입니다.

수직 동기화는 새 그림을 바꿔 끼우는 때를 이 박자에 맞춥니다. 모니터가 한 화면을 다 칠한 뒤, 다음 칠하기를 시작하기 직전에만 그림을 바꿉니다. 그래서 한 장이 예산을 조금만 넘겨도 다음 박자까지 기다립니다.

60 Hz 에서 18밀리초 걸린 장은 16.7밀리초 박자를 놓치고 33.3밀리초 때 화면에 나옵니다. 그동안 모니터는 앞 장을 한 번 더 칠합니다. 보는 사람에게는 화면이 멈칫한 것으로 보입니다.

수직 동기화를 끄면 다 그린 그림을 기다리지 않고 바로 바꿔 끼웁니다. 모니터가 칠하는 도중에 바뀌면 한 화면에 두 장이 섞여 보입니다. 위쪽은 앞 장, 아래쪽은 새 장입니다. 이 결함이 화면 찢어짐(tearing)입니다.

아래 그림은 한 장을 다 그린 뒤 화면에 나오기까지 갈리는 길을 한데 모은 것입니다.

flowchart TD
    A[한 장을 다 그렸다] --> B{수직 동기화가 켜졌나}
    B -->|예| C{예산 안에 끝났나}
    C -->|예| D[다음 박자에 화면에 나온다]
    C -->|아니오| E[박자를 하나 놓친다 · 앞 장이 한 번 더 보인다]
    B -->|아니오| F[바로 바꿔 끼운다 · 칠하는 도중이면 화면이 찢어진다]

거꾸로 모니터가 그림을 기다리는 방식도 있습니다. 새 그림이 도착할 때마다 그때 새로 칠합니다. 이 방식이 가변 주사율(variable refresh rate)입니다. 예산을 조금 넘긴 장도 다음 박자까지 기다리지 않고 넘긴 만큼만 늦게 나옵니다.

평균이 가리는 한 장

프레임 예산은 평균이 아니라 튀는 장까지 한 장 한 장에 걸립니다. 1초에 60장을 그렸어도 그중 한 장이 50밀리초 걸렸다면 그동안 화면은 멈춰 있었습니다. 보는 사람은 이 멈춤을 끊김으로 느낍니다.

한 장만 튀는 원인은 대개 드물게 도는 오래 걸리는 일입니다. 쓰지 않는 메모리를 한꺼번에 치우는 가비지 컬렉션이 그 장 도중에 돌 수 있습니다. 디스크에서 파일을 읽느라 기다리는 장도 있습니다.

그래서 예산을 지켰는지는 백분위수로 봅니다. 프레임 시간을 짧은 것부터 줄 세우고 99% 지점의 값을 예산과 견줍니다. 평균에 묻히는 오래 걸린 장을 드러내려고 이 값을 봅니다. 백엔드에서 드물게 오래 걸린 요청을 꼬리 지연으로 따로 보는 것과 같은 까닭입니다.

예산을 다 쓰지 않는 까닭

16.7밀리초가 전부 내 코드의 몫은 아닙니다. 같은 시간 안에 운영체제와 그래픽 드라이버도 제 일을 합니다. 게임 엔진이나 브라우저 위에서 돈다면 그 바탕 프로그램도 한 장마다 제 일에 시간을 씁니다.

휴대 기기는 계산을 쉬지 않고 오래 하면 뜨거워집니다. 그러면 열을 줄이려고 처리 속도를 스스로 낮춥니다. 이것이 열 스로틀링(thermal throttling)입니다. 같은 일이 더 오래 걸리니 처음에 예산 안이던 장도 넘기게 됩니다.

그래서 예산을 끝까지 채우지 않고 여유를 남겨 두는 편이 흔합니다.

예산에 맞추는 방법

한 장이 예산을 넘기면 손쓸 방법은 넷으로 묶입니다.

방법 무엇을 하나 흔한 예
일을 덜기 그릴 것과 계산할 것을 줄입니다 화면 밖 물체를 그리지 않는 컬링 · 멀리 있는 물체를 단순한 모양으로 그리는 LOD(Level of Detail, 세부 수준) · 해상도 낮추기
여러 장에 나누기 한 장에 못 끝낼 일을 여러 장에 조금씩 나눕니다 길 찾기를 한 장에 몇 캐릭터씩만 계산하는 타임 슬라이싱
다른 흐름으로 옮기기 화면을 그리는 흐름 밖에서 돌립니다 파일 읽기를 배경 스레드에서 하기
예산 늘리기 목표 프레임률을 낮춥니다 60 FPS 대신 30 FPS 를 목표로 잡아 예산을 두 배로

앞의 셋은 한 장의 일을 줄입니다. 마지막은 한 장의 한도를 늘립니다. 한도를 늘리면 1초에 보이는 장수가 줄어 움직임이 덜 매끄러워집니다.

게임 밖의 프레임 예산

브라우저도 스크롤과 애니메이션을 한 장씩 그립니다. 흔한 화면이 1초에 60번 새로 칠하므로 한 장의 예산도 16.7밀리초입니다. 그 안에 자바스크립트 실행도, 화면을 그리는 일도 모두 들어가야 합니다.

브라우저에서 자바스크립트와 화면 그리기는 대개 한 스레드에서 돕니다. 이 스레드가 메인 스레드입니다. 오래 걸리는 스크립트가 이 스레드를 붙잡으면 그 장은 예산을 넘깁니다.

이렇게 예산을 넘긴 장 때문에 스크롤이나 애니메이션이 덜컥거리는 것을 웹 개발자들은 jank라고 부릅니다. 게임에서 말하는 끊김과 같은 현상입니다.

브라우저는 다음 장을 그리기 직전에 부를 함수를 맡기는 방법을 줍니다. 이 방법이 requestAnimationFrame입니다. 한 장에 한 번 불리므로 애니메이션 계산을 장마다 한 번씩 예산 안에 넣기 쉽습니다.

가상 현실 헤드셋은 예산이 더 빠듯합니다. 흔히 90 FPS 를 목표로 잡아 한 장의 예산이 11.1밀리초입니다. 머리를 돌렸는데 화면이 늦게 따라오면 보는 사람이 어지러움을 느끼기 때문입니다.

거꾸로 화면이 움직이지 않는 동안에는 이 예산을 따질 일이 적습니다. 멈춘 화면에서 버튼을 눌러 결과가 뜨는 일은 한 장의 시간보다 사람이 기다림을 느끼는지로 잽니다. 프레임 예산은 스크롤과 애니메이션과 게임처럼 매 장이 이어져 움직임을 만드는 동안에 걸리는 한도입니다.

관련 항목

프레임 예산을 셈하는 속도 지표

프레임 · 프레임률 · 프레임 시간 · 주사율 · 헤르츠 · 프레임 페이싱

예산을 넘긴 장이 화면에 남기는 결함

프레임 드롭 · 티어링 · jank · 입력 지연

예산 안에서 한 장을 만드는 흐름

게임 루프 · 렌더 파이프라인 · 렌더링 · 델타 타임 · 고정 타임스텝

예산을 넘긴 장치를 가려내는 병목 이름과 도구

CPU 바운드 · GPU 바운드 · CPU · GPU · 프로파일러 · 프로파일링

한 장을 예산 안에 넣는 기법

컬링 · LOD · 해상도 · 해상도 스케일링 · 타임 슬라이싱 · 스레드 · 오브젝트 풀링

한 장의 프레임 시간을 튀게 하는 원인

가비지 컬렉션 · 서멀 스로틀링 · 셰이더 컴파일 · 디스크 입출력

예산과 화면 표시의 박자를 맞추는 기술

수직 동기화 · 가변 주사율 · 더블 버퍼링 · 트리플 버퍼링 · 모니터 · 픽셀

브라우저에서 같은 예산을 다루는 개념

메인 스레드 · requestAnimationFrame · 이벤트 루프 · 레이아웃 · 브라우저

백엔드에서 같은 꼴로 시간을 나누는 지표

지연 예산 · 꼬리 지연 · 백분위수 · 서비스 수준 목표 · 타임아웃

프레임 예산을 쓰는 상위 분야

게임 개발 · 실시간 렌더링 · 가상 현실 · 애니메이션 · 그래픽스

다른 이름: frame budget · frame time budget · 프레임 시간 예산 · 프레임당 예산