로드 연산
고친 사람 github-actions[bot]
로드 연산은 그리기를 시작할 때 이미지에 남아 있던 그림을 어떻게 할지 정합니다. 그래픽 카드는 이 답을 보고 쓸모없는 읽기를 건너뜁니다. 컴퓨터 구조에서는 프로세서가 메모리 값을 읽어 오는 명령도 같은 이름으로 부릅니다.
쉽고 빠른 이해
그리기 묶음을 열 때 「이 이미지에 있던 그림이 필요한가」를 미리 알리는 설정입니다. 앞에서 그린 게임 화면 위에 점수 숫자만 덧그린다면 있던 그림이 필요합니다.
미리 알리지 않으면 그래픽 카드는 있던 그림을 쓸지 몰라 매번 읽어 둡니다. 화면 한 장을 읽는 일이 초마다 수십 번 쌓입니다.
- 있던 그림 위에 이어 그릴 때는 「불러오기」를 고릅니다
- 새 장면을 그릴 때는 「한 색으로 지우기」를 고릅니다
- 화면 전체를 다시 칠할 때는 「상관없음」을 고릅니다
대가는 고른 값이 틀리면 화면이 깨진다는 점입니다. 「상관없음」을 골라 놓고 칠하지 않은 곳이 있으면 거기에 알 수 없는 값이 보입니다.
상세
그리기를 열 때 내리는 결정
회의실에 들어가면 화이트보드에 지난 회의의 판서가 남아 있습니다. 이어서 쓸 내용이면 그대로 두고 씁니다. 새 회의면 지우개로 지우고 시작합니다. 어차피 보드 전체를 큰 포스터로 덮을 거라면 지울지 말지 고민할 필요가 없습니다.
그래픽 카드도 그리기를 시작할 때 같은 결정을 내립니다. 이 결정을 따라가려면 먼저 그래픽 카드가 그림을 어디에, 어떤 묶음으로 그리는지 알아야 합니다.
그래픽 카드에서 그리기를 맡는 칩이 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 그림 한 장을 이루는 수많은 점을 한꺼번에 계산하도록 만든 칩입니다.
프로그램이 GPU 에 일을 시키는 창구를 그래픽스 API(Application Programming Interface, 응용 프로그래밍 인터페이스)라고 부릅니다. 프로그램은 이 창구에 그리기 명령을 적어 GPU 에 넘깁니다.
GPU 가 그림을 그리는 곳은 이미지입니다. 이미지는 화면의 점 하나(픽셀)마다 값을 하나씩 담습니다. 그리기 명령은 이 픽셀들의 값을 채웁니다.
많은 그래픽스 API 는 그리기 명령을 렌더 패스라는 묶음으로 받습니다. 묶음을 열 때 어느 이미지에 그릴지 알립니다. 닫을 때까지 들어온 명령은 전부 그 이미지에 그립니다. 묶음 단위로 받으면 GPU 가 메모리를 오가는 순서를 미리 짤 수 있습니다.
렌더 패스에 걸어 둔 이미지를 어태치먼트라고 부릅니다. 패스에 붙여 둔 이미지라는 뜻입니다. 한 패스에 어태치먼트를 여럿 걸 수 있습니다.
화면에 보일 색을 받는 어태치먼트가 렌더 타깃입니다. 렌더 타깃의 픽셀에는 색이 들어갑니다.
물체의 앞뒤를 가리는 데 쓰는 어태치먼트가 깊이 버퍼입니다. 깊이 버퍼의 픽셀에는 카메라에서 떨어진 거리가 들어갑니다. 새로 그리는 점이 이미 적힌 거리보다 가까울 때만 그 점을 칠합니다.
로드 연산은 렌더 패스를 열 때 어태치먼트마다 고르는 값입니다. 그 이미지에 이미 들어 있던 내용을 어떻게 다룰지 정합니다. 회의실의 판서가 이미지에 남은 내용입니다.
패스를 닫을 때도 짝이 되는 값을 고릅니다. 다 그린 결과를 남길지 버릴지 정하는 값이 스토어 연산입니다. 로드 연산은 시작을, 스토어 연산은 끝을 맡습니다.
고를 수 있는 세 값
값은 대개 셋입니다. 이름은 그래픽스 API 마다 조금씩 다르지만 뜻은 같습니다. 화이트보드에 견주면 판서를 그대로 두고 쓰는 것이 load, 지우개로 지우는 것이 clear, 포스터로 덮는 것이 don't care 입니다.
| 값 | 패스를 열 때 하는 일 | 고르는 때 |
|---|---|---|
| load | 있던 내용을 불러와 그 위에 이어 그린다 | 앞 패스의 그림 위에 덧그릴 때 |
| clear | 정해 둔 한 값으로 모든 픽셀을 채운다 | 새 장면을 처음부터 그릴 때 |
| don't care | 있던 내용을 신경 쓰지 않는다 | 패스가 모든 픽셀을 새로 칠할 때 |
clear 를 고르면 채울 값도 함께 적습니다. 이 값을 클리어 값이라고 부릅니다. 렌더 타깃이면 배경색을 적습니다. 깊이 버퍼면 카메라에서 가장 먼 거리를 적습니다. 깊이를 가장 멀게 채워 두어야 처음 그리는 물체가 무엇이든 앞에 있는 것으로 판정됩니다.
don't care 는 「무엇이 들어 있든 상관없다」는 약속입니다. 그래서 패스를 연 직후 픽셀마다 무엇이 들어 있을지 알 수 없습니다. 앞서 그린 그림이 남아 있을 수도 있고 뜻 없는 값이 있을 수도 있습니다.
값은 어태치먼트마다 따로 고릅니다. 한 패스 안에서 렌더 타깃은 load 로 열고 깊이 버퍼는 clear 로 열 수 있습니다.
읽기를 아끼는 원리
로드 연산을 미리 알리면 왜 빨라지는지는 GPU 의 메모리 구조에서 나옵니다.
GPU 가 쓰는 이미지는 주 메모리에 있습니다. 주 메모리는 크지만 칩 바깥에 있어 오가는 데 시간과 전력이 듭니다. 칩 안에도 작은 메모리가 있습니다. 작지만 금방 닿습니다.
타일 기반 렌더링을 쓰는 GPU 는 화면을 작은 사각형 조각으로 나눠 그립니다. 이 조각이 타일입니다. 타일 하나를 칩 안 메모리에 올려 둡니다. 거기 걸리는 그리기를 다 마친 뒤 다음 타일로 넘어갑니다. 휴대폰의 GPU 가 대개 이 방식입니다.
이런 GPU 에서 세 값은 타일을 칩 안에 처음 올리는 방법을 가릅니다. load 만 주 메모리를 읽습니다. clear 는 읽지 않고 칩 안에서 바로 한 값으로 칠합니다. don't care 는 읽지도 칠하지도 않습니다.
flowchart TD
subgraph MAIN["주 메모리 · 크고 멀다"]
IMG["어태치먼트 이미지"]
end
subgraph CHIP["칩 안 메모리 · 같은 타일을 올리는 세 경우"]
T1["load · 타일에 있던 내용"]
T2["clear · 한 값으로 칠한 타일"]
T3["don't care · 아무것도 안 한 타일"]
end
IMG -->|"읽어 온다"| T1
그림에서 주 메모리와 이어진 화살표는 load 하나뿐입니다. clear 와 don't care 는 그 화살표가 없습니다. 타일마다 아낀 읽기가 모이면 이미지 한 장을 통째로 안 읽는 셈이 됩니다.
양을 어림해 보면 이렇습니다. 가로 1920 · 세로 1080 픽셀에 픽셀마다 4바이트를 쓰는 이미지는 한 장이 약 8MB 입니다. 초에 60번 그릴 때 매번 load 로 열면 초마다 약 500MB 를 읽습니다. 처음부터 다시 그릴 화면이라면 이 읽기는 전부 버리는 일입니다.
렌더 패스가 없는 옛 그래픽스 API 에는 로드 연산을 적을 곳이 없습니다. 프로그램이 알려 주지 않으니 있던 내용이 필요한지 누군가 대신 짐작해야 합니다.
그 짐작은 드라이버가 맡습니다. 드라이버는 그래픽스 API 의 명령을 GPU 가 알아듣는 일로 바꿔 주는 소프트웨어입니다. 드라이버는 앞뒤 명령을 보고 짐작합니다. 짐작이 빗나가면 안 해도 될 읽기가 생깁니다.
화면을 조각내지 않는 GPU 에서는 차이가 작은 편입니다. 이런 GPU 는 처음부터 주 메모리의 이미지에 바로 그려서 타일을 올리는 단계가 없습니다. 데스크톱용 GPU 가 대개 그렇습니다.
값을 고르는 기준
고르는 순서는 질문 둘로 정리됩니다. 먼저 있던 그림이 필요한지 묻습니다. 필요 없으면 모든 픽셀을 다시 칠하는지 묻습니다.
flowchart TD
Q1{"있던 그림을 이어 그리나"}
Q1 -->|예| L["load"]
Q1 -->|아니오| Q2{"모든 픽셀을 빠짐없이 다시 칠하나"}
Q2 -->|예| D["don't care"]
Q2 -->|아니오| C["clear"]
장면을 새로 그리는 첫 패스는 대개 clear 입니다. 하늘이나 배경이 모든 픽셀을 덮는다고 확신하기 어려우면 clear 가 안전합니다. 타일 기반 GPU 에서 clear 는 읽기가 없어서 don't care 와 비용 차이가 작습니다.
don't care 가 맞는 때는 패스가 화면 전체를 한 번에 덮을 때입니다. 다 그린 그림 전체에 흐림이나 색 보정을 거는 후처리 패스가 흔한 예입니다. 이 패스는 모든 픽셀을 새로 계산해 덮어씁니다.
load 는 앞 패스의 결과 위에 무언가를 얹을 때만 고릅니다. 게임 화면을 다 그린 뒤 점수 숫자를 덧그리는 패스가 그렇습니다. 숫자가 덮지 않는 픽셀에는 앞 패스의 그림이 남아야 합니다.
흔한 실수는 두 갈래입니다. 처음부터 다시 그릴 이미지를 load 로 열면 쓰지도 않을 내용을 읽습니다. 결과는 맞지만 느립니다.
반대로 칠하지 않는 픽셀이 있는데 don't care 를 고르면 그 픽셀에 알 수 없는 값이 남습니다. 어떤 GPU 에서는 직전에 그린 그림이 남아 멀쩡해 보입니다. 다른 GPU 에서는 같은 프로그램이 깨진 조각을 보입니다. 한 기계에서 시험해서는 잡히지 않는 버그가 됩니다.
두 패스로 본 로드 연산
아래는 게임 화면을 그리고 그 위에 점수를 덧그리는 두 패스입니다. 어느 그래픽스 API 의 문법도 아닙니다. 흐름만 보인 코드입니다. 오른쪽 주석은 점수 숫자가 찍히는 픽셀 하나에 그 줄을 지난 뒤 담긴 색입니다.
begin_pass(화면, clear(검정)) // 검정
draw(하늘) // 하늘색
end_pass(화면, store)
begin_pass(화면, load) // 하늘색
draw(점수) // 흰색
end_pass(화면, store)
첫 패스는 clear 로 열어 픽셀을 검정으로 채운 뒤 하늘을 그립니다. 스토어 연산을 store 로 두어 하늘색이 주 메모리에 남습니다.
둘째 패스는 load 로 열어 남아 있던 하늘색을 불러옵니다. 점수 숫자가 그 위에 흰색으로 찍힙니다. 숫자가 닿지 않는 이웃 픽셀에는 하늘색이 남습니다.
둘째 패스를 clear 로 열었다면 하늘이 지워집니다. 화면에는 검은 바탕에 흰 숫자만 남습니다. don't care 로 열었다면 숫자 바깥 픽셀에 무엇이 보일지 알 수 없습니다.
같은 이름의 CPU 명령
CPU(Central Processing Unit, 중앙 처리 장치) 쪽에서 로드 연산은 전혀 다른 일을 가리킵니다. 메모리에 있는 값을 레지스터로 읽어 오는 명령입니다. 레지스터는 CPU 안에 있는 아주 작은 저장 칸입니다. 계산은 여기 올라온 값으로만 합니다.
반대 방향으로 레지스터 값을 메모리에 써 넣는 명령은 스토어라고 부릅니다. 메모리는 로드와 스토어로만 만지고 계산은 레지스터끼리만 하게 짠 설계를 로드-스토어 아키텍처라고 부릅니다. 컴퓨터 구조 교재에서 「로드 연산」을 만나면 대개 이 뜻입니다.
관련 항목
로드 연산과 짝을 이루는 패스 설정
스토어 연산 · 클리어 값 · 멀티샘플 리졸브 · 렌더 패스 · 서브패스 · 다이내믹 렌더링
로드 연산을 고르는 대상인 이미지
어태치먼트 · 렌더 타깃 · 깊이 버퍼 · 스텐실 버퍼 · 색 버퍼 · 프레임버퍼 · 이미지 뷰
로드 연산이 비용을 줄이는 GPU 구조
GPU · 타일 기반 렌더링 · 타일 메모리 · 즉시 모드 렌더링 · 메모리 대역폭 · 디바이스 드라이버
로드 연산을 잘못 골라 생기는 문제
미정의 동작 · 렌더 패스 데이터 레이스 · 피드백 루프 · 오버드로
로드 연산을 패스 설정으로 받는 그래픽스 API
Vulkan · Metal · WebGPU · Direct3D 12 · 그래픽스 API
로드 연산을 고르는 흔한 그리기 단계
후처리 · 그림자 매핑 · 지연 셰이딩 · 멀티패스 렌더링 · 렌더 그래프
CPU 에서 같은 이름으로 부르는 명령
로드-스토어 아키텍처 · 레지스터 · 명령어 집합 · RISC · 메모리 접근
로드 연산이 속하는 상위 분류
그래픽스 · 렌더링 · 실시간 렌더링 · 그래픽스 파이프라인
다른 이름: load operation · load op · loadOp · load action · 로드 오퍼레이션