입력 어셈블러
고친 사람 github-actions[bot]
입력 어셈블러는 그림을 그리는 여러 단계 가운데 맨 앞에서 그릴 재료를 꺼내 옵니다. 메모리에 쌓인 꼭짓점 값을 한 점씩 잘라 읽습니다. 어느 점끼리 한 도형인지 정하는 규칙도 함께 붙여 보냅니다. 뒤따르는 단계들은 이 단계가 차려 준 점과 규칙만 받아서 일합니다.
쉽고 빠른 이해
입력 어셈블러는 메모리에 올려 둔 꼭짓점 값을 한 점씩 읽어 내는 단계입니다. 사각형 하나를 그리라고 하면 꼭짓점을 읽어 냅니다. 이 점들을 삼각형 둘로 묶으라는 규칙도 붙입니다.
이 단계가 없으면 그리는 프로그램마다 바이트 덩이를 직접 잘라 읽어야 합니다. 어디까지가 한 점이고 어느 점끼리 한 도형인지도 매번 스스로 따져야 합니다.
어떻게 도나:
- 프로그램이 값을 읽는 법과 점을 묶는 규칙을 미리 알려 둡니다
- 그리라는 명령이 오면 알려 둔 대로 메모리에서 점을 하나씩 잘라 냅니다
- 점은 하나씩 다음 단계로 넘깁니다. 묶는 규칙은 뒤 단계까지 따라갑니다
- 점들은 그 뒤에서 규칙대로 삼각형 같은 도형으로 묶입니다
대가가 있습니다. 코드를 넣을 수 없습니다. 정해진 설정 중에서 고르기만 합니다. 미리 알려 둔 모양과 다른 방식으로 값을 읽고 싶으면 이 단계를 비켜 가야 합니다.
상세
이 절은 입력 어셈블러가 무엇을 받아 무엇을 넘기는지 봅니다. 사각형 하나를 화면에 그리는 예를 처음부터 끝까지 씁니다.
식당 주방의 재료 준비 담당을 떠올리면 가깝습니다. 창고에서 재료를 주문서대로 하나씩 꺼내 손질 담당에게 넘깁니다. 주문서는 재료 뒤를 따라가 접시에 담는 사람에게 닿습니다. 손질이 끝난 재료는 그 주문서대로 한 접시씩 담깁니다.
파이프라인의 첫 단계
화면에 그림을 그리는 일은 GPU(Graphics Processing Unit, 그래픽 처리 장치)가 맡습니다. GPU 는 그리기를 여러 단계로 쪼개 차례로 넘깁니다. 이렇게 이어진 단계 전체가 그래픽스 파이프라인입니다. 입력 어셈블러는 그 첫 단계입니다.
이 이름은 Direct3D에서 왔습니다. Direct3D 는 마이크로소프트가 윈도우용으로 만든 그리기 API(Application Programming Interface, 응용 프로그램 인터페이스)입니다. Vulkan과 OpenGL에도 같은 일을 하는 단계가 있습니다. 부르는 이름만 다릅니다.
입력 어셈블러는 고정 기능 단계입니다. 할 일은 칩에 이미 정해져 있습니다. 프로그램은 코드를 넣을 수 없습니다. 정해진 설정값 중에서 고르기만 합니다.
바로 뒤에는 정점 셰이더가 섭니다. 셰이더는 GPU 에서 도는 작은 프로그램입니다. 정점 셰이더는 점 하나마다 한 번씩 돌아 그 점이 화면 어디에 놓일지 계산합니다. 입력 어셈블러는 이 셰이더가 받을 점을 한 개씩 마련해 줍니다.
이 단계를 따로 두는 까닭은 셰이더가 계산에만 매달리게 하려는 것입니다. 바이트를 어디서 끊어 한 점으로 읽을지, 어느 점끼리 한 도형인지는 셰이더마다 따질 일이 아닙니다. 그 일을 입력 어셈블러가 한곳에서 도맡습니다.
무엇을 받아 무엇을 넘기나
입력 어셈블러가 다루는 점은 정점입니다. 영어 이름은 vertex 입니다. 소리 나는 대로 버텍스라고도 씁니다.
정점은 도형의 꼭짓점 하나에 딸린 값 묶음입니다. 위치가 기본입니다. 색처럼 그 꼭짓점에 붙은 값이 함께 들어가기도 합니다.
프로그램은 그리기 전에 네 가지를 넘겨 둡니다. 아래 표가 넷의 이름과 하는 일입니다. 넷은 뒤 소절에서 하나씩 차례로 봅니다.
| 이름 | 하는 일 |
|---|---|
| 정점 버퍼 | 정점 값이 든 바이트 덩이 |
| 인덱스 버퍼 | 정점 번호 목록. 없어도 그릴 수 있다 |
| 입력 레이아웃 | 바이트를 자르는 법 |
| 프리미티브 토폴로지 | 점을 묶는 규칙 |
이 넷이 입력 어셈블러로 모인 뒤 어디로 흘러가는지는 아래 그림과 같습니다.
flowchart TD
subgraph IN["프로그램이 미리 넘겨 두는 것"]
VB["정점 버퍼"]
IB["인덱스 버퍼"]
IL["입력 레이아웃"]
TP["프리미티브 토폴로지"]
end
IN --> IA["입력 어셈블러"]
IA -->|"정점을 하나씩"| VS["정점 셰이더"]
IA -.->|"묶는 규칙"| PA["도형으로 묶기"]
VS --> PA
PA --> NX["래스터화 같은 뒤 단계"]
정점 셰이더에는 정점이 하나씩 따로 갑니다. 점을 묶는 규칙은 정점 셰이더 옆으로 건너가 뒤 단계에 닿습니다. 정점 셰이더를 마친 점들은 거기서 그 규칙대로 도형으로 묶입니다. 그래서 래스터화 같은 뒤 단계는 낱낱의 점이 아니라 묶인 도형을 받습니다.
바이트 덩이를 정점으로 자르기
정점 버퍼는 정점 값을 GPU 쪽 메모리에 한 덩이로 올려 둔 것입니다. 덩이 안에는 숫자가 빈틈없이 이어져 있을 뿐입니다. 어디서 한 정점이 끝나고 다음 정점이 시작하는지는 덩이에 적혀 있지 않습니다.
그 경계를 알려 주는 설명서가 입력 레이아웃입니다. 한 정점 안에 어떤 값이 몇 번째 바이트부터 어떤 형식으로 들어 있는지 적습니다. 사각형 예에서 정점 하나가 위치 둘과 색 셋을 소수로 담는다면 이렇게 적힙니다.
| 값 | 형식 | 정점 안에서 시작하는 바이트 |
|---|---|---|
| 위치 | 소수 둘 · 8바이트 | 0번째 |
| 색 | 소수 셋 · 12바이트 | 8번째 |
두 값을 더하면 한 정점이 20바이트입니다. 입력 어셈블러는 덩이를 20바이트씩 끊어 읽습니다. 다음 정점까지 건너뛰는 이 폭이 스트라이드입니다.
잘라 낸 값은 정점 셰이더의 입력과 이름으로 짝지어집니다. HLSL(High Level Shader Language, 고수준 셰이더 언어)은 Direct3D 에서 셰이더를 짜는 언어입니다. HLSL 에서는 셰이더 입력마다 시맨틱이라는 꼬리표를 답니다. 레이아웃 쪽에도 같은 꼬리표를 적어 두면 둘이 맞물립니다.
struct VSInput {
float2 pos : POSITION; // 0~7바이트
float3 color : COLOR; // 8~19바이트
};
POSITION 과 COLOR 가 꼬리표입니다. 주석은 각 값이 정점 하나에서 몇 번째 바이트를 읽는지
보입니다. 레이아웃과 셰이더 입력이 서로 안 맞으면 그리기 설정을 만드는 단계에서 오류가 나기도
합니다. 엉뚱한 바이트를 색으로 읽어 화면이 이상하게 나오기도 합니다.
버퍼 여럿과 인스턴싱
정점 값을 버퍼 하나에 몰아 둘 필요는 없습니다. 위치는 한 버퍼에, 색은 다른 버퍼에 둘 수 있습니다. 입력 어셈블러에는 버퍼를 하나씩 꽂는 번호 붙은 칸이 여럿 있습니다. 이 칸이 슬롯입니다. 레이아웃은 값마다 몇 번 슬롯에서 읽을지를 적습니다.
슬롯을 여럿 쓰면 인스턴싱을 할 수 있습니다. 인스턴싱은 같은 모양을 수백 번 그리는 방식입니다. 모양은 한 번만 넘깁니다. 그릴 때마다 달라지는 값만 따로 넘깁니다.
숲에 나무 천 그루를 세우는 장면이 대표 예입니다. 나무 모양의 정점은 한 버퍼에 둡니다. 나무마다 다른 위치는 다른 버퍼에 둡니다.
레이아웃은 두 번째 버퍼를 정점마다가 아니라 나무 한 그루마다 한 칸씩 넘기라고 적습니다. 그러면 그리기 명령 한 번으로 나무 천 그루가 저마다 다른 곳에 섭니다.
점을 도형으로 묶는 규칙
잘라 낸 정점만으로는 어느 점끼리 한 도형인지 알 수 없습니다. 그 묶는 규칙이 프리미티브 토폴로지입니다. 프리미티브는 점·선·삼각형처럼 GPU 가 한 번에 그리는 기본 도형입니다. 토폴로지는 정점 줄을 어떤 프리미티브로 끊을지 정합니다.
같은 정점 여섯 개라도 규칙에 따라 나오는 도형이 달라집니다.
| 토폴로지 | 끊는 법 | 정점 여섯 개를 넣으면 |
|---|---|---|
| 점 목록 | 정점 하나가 점 하나 | 점 6개 |
| 선 목록 | 둘씩 끊어 선 하나 | 선 3개 |
| 선 스트립 | 이웃한 두 점마다 선 하나 | 선 5개 |
| 삼각형 목록 | 셋씩 끊어 삼각형 하나 | 삼각형 2개 |
| 삼각형 스트립 | 새 점 하나가 앞의 두 점과 삼각형을 이룸 | 삼각형 4개 |
삼각형 스트립이 적은 점으로 많은 삼각형을 내는 까닭은 앞 삼각형의 변을 다음 삼각형이 이어받기 때문입니다. 0·1·2 번 점이 첫 삼각형이면 1·2·3 번이 둘째, 2·3·4 번이 셋째입니다. 땅바닥처럼 삼각형이 줄지어 붙은 면을 그릴 때 정점을 아낍니다.
토폴로지에는 정점 셰이더 뒤에 골라 붙이는 단계를 위한 종류도 있습니다. 지오메트리 셰이더는 도형을 받아 새 도형을 만들어 내는 단계입니다. 이 단계가 이웃 삼각형을 봐야 할 때는 이웃의 꼭짓점까지 딸려 보내는 토폴로지를 고릅니다.
테셀레이션은 도형을 잘게 쪼개 면을 더 매끄럽게 만드는 단계입니다. 이 단계를 쓸 때는 정점 여러 개를 한 덩이의 패치로 묶는 토폴로지를 고릅니다.
인덱스로 같은 정점을 다시 쓰기
사각형을 삼각형 목록으로 그리면 삼각형 둘에 정점 여섯이 듭니다. 그런데 사각형의 꼭짓점은 넷뿐입니다. 두 삼각형이 맞닿은 대각선 양 끝의 정점 둘이 두 번씩 들어가는 셈입니다.
인덱스 버퍼는 이 중복을 걷어 냅니다. 정점 버퍼에는 서로 다른 꼭짓점 넷만 담습니다. 어느 꼭짓점을 어느 차례로 쓸지는 번호 목록으로 따로 적습니다. 번호 목록이 0·1·2·0·2·3 이면 첫 삼각형은 0·1·2 번, 둘째 삼각형은 0·2·3 번 꼭짓점을 씁니다.
인덱스 버퍼가 있으면 입력 어셈블러는 번호를 먼저 읽습니다. 그 번호의 정점을 정점 버퍼에서 찾아 옵니다.
정점 하나는 20바이트입니다. 번호 하나는 2바이트나 4바이트입니다. 그래서 겹치는 정점이 많은 모델일수록 메모리를 크게 아낍니다.
번호로 가져오면 계산도 줄일 수 있습니다. 같은 번호가 다시 나오면 GPU 는 그 정점에 대해 정점 셰이더를 또 돌리지 않고 방금 낸 결과를 다시 쓸 수 있습니다. 이 결과를 잠시 들고 있는 곳이 정점 캐시입니다.
입력 어셈블러가 붙이는 번호
입력 어셈블러는 버퍼에 없는 값도 만들어 붙입니다. 정점마다 몇 번째 정점인지, 인스턴싱 중이면 몇 번째 인스턴스인지를 번호로 달아 정점 셰이더에 넘깁니다. 뒤 단계에는 몇 번째 도형인지도 번호로 전해집니다.
이 번호들이 시스템 값입니다. 프로그램이 넘긴 값이 아니라 파이프라인이 만들어 주는 값입니다. 셰이더는 이 번호로 나무마다 다른 색을 고를 수 있습니다.
이 단계를 비켜 가는 그리기
입력 어셈블러가 읽을 수 있는 것은 레이아웃에 적을 수 있는 모양뿐입니다. 값이 고정된 형식으로 고정된 간격마다 놓여 있어야 합니다. 압축된 정점이나 모양이 제각각인 데이터는 이 틀에 안 맞습니다.
그래서 레이아웃을 비워 두고 셰이더가 버퍼를 직접 읽는 방식이 있습니다. 입력 어셈블러는 정점 번호만 넘깁니다. 정점 셰이더가 그 번호로 버퍼의 몇 번째 값을 읽을지 스스로 계산합니다.
이 방식이 버텍스 풀링입니다. 셰이더가 정점(버텍스)을 스스로 끌어온다(pull)는 뜻의 이름입니다. 읽는 법이 자유로워지는 대신 바이트를 자르는 일을 셰이더 코드가 떠맡습니다.
아예 입력 어셈블러가 없는 파이프라인도 있습니다. 메시 셰이더를 쓰는 파이프라인은 입력 어셈블러와 정점 셰이더를 걷어 냅니다. 그 대신 메시 셰이더가 정점과 도형을 직접 만들어 냅니다. 그림과 상관없는 계산을 GPU 에 시키는 컴퓨트 셰이더도 이 단계를 거치지 않습니다.
관련 항목
입력 어셈블러가 속하는 파이프라인과 그 뒤 단계
그래픽스 파이프라인 · 고정 기능 단계 · 정점 셰이더 · 테셀레이션 · 지오메트리 셰이더 · 프리미티브 조립 · 래스터화 · 픽셀 셰이더 · 출력 병합기
입력 어셈블러가 읽어 들이는 데이터
정점 · 정점 버퍼 · 인덱스 버퍼 · 정점 속성 · 입력 레이아웃 · 스트라이드 · 오프셋 · 텍스처
입력 어셈블러가 정점을 묶는 도형 규칙
프리미티브 · 프리미티브 토폴로지 · 삼각형 목록 · 삼각형 스트립 · 선 스트립 · 인접 정보 프리미티브 · 패치
입력 어셈블러가 셰이더에 넘기는 값과 기법
시맨틱 · 시스템 값 · 슬롯 · 인스턴싱 · 정점 캐시 · 드로 콜 · HLSL
입력 어셈블러를 설정하는 그래픽스 API
Direct3D · Vulkan · OpenGL · Metal · WebGPU · API
입력 어셈블러를 거치지 않는 그리기 방식
다른 이름: input assembler · input-assembler stage · 입력 조립기