파이프라인 라이브러리
고친 사람 github-actions[bot]
파이프라인 라이브러리는 그래픽 카드용 파이프라인을 조각으로 나눠 미리 만들어 두게 해 줍니다. 필요한 조각을 모아 이어 붙여야 쓸 수 있는 파이프라인이 됩니다. 처음 보는 조합이 나와도 처음부터 컴파일하지 않고 조각을 잇기만 하면 됩니다.
쉽고 빠른 이해
그래픽 카드가 쓸 작은 프로그램과 그리기 설정을 묶어 컴파일한 덩어리를 파이프라인이라고 부릅니다. 파이프라인 라이브러리는 이 덩어리를 조각으로 나눠 미리 컴파일해 둡니다. 필요할 때 조각을 이어 붙여 씁니다. 물체 모양을 다루는 조각 하나와 색을 칠하는 조각 여럿을 따로 만들어 두고 짝지어 쓰는 식입니다.
파이프라인은 프로그램과 설정의 조합마다 하나씩 필요합니다. 조합이 수천 가지면 전부 미리 만들 수 없습니다. 그리는 도중에 처음 보는 조합을 통째로 컴파일하면 화면이 잠깐 멈칫합니다.
어떻게 도나:
- 조각마다 따로 컴파일해 둡니다
- 새 조합이 필요해지면 조각을 빠르게 이어 붙여 바로 씁니다
- 여유가 생기면 더 꼼꼼하게 다듬어 이은 것으로 바꿉니다
대가도 있습니다. 빠르게 이은 것은 통째로 만든 것보다 그릴 때 조금 느릴 수 있습니다. 조각끼리 주고받을 값을 미리 맞춰야 해서 코드가 복잡해집니다. 그래픽 카드 쪽 소프트웨어가 이 기능을 지원해야 씁니다.
조합이 적고 미리 다 알면 쓸 까닭이 적습니다. 로딩 때 통째로 다 만들어 두면 됩니다.
상세
이 절은 파이프라인 라이브러리가 무엇을 조각으로 나누는지, 조각을 어떻게 잇는지, 그래서 무엇을 아끼는지를 봅니다. 색칠 방식을 100가지 쓰는 게임 하나를 줄곧 예로 씁니다.
샌드위치 가게는 주문이 들어올 때마다 빵을 굽지 않습니다. 빵과 속재료와 소스를 미리 준비해 둡니다. 주문이 오면 고른 것을 쌓기만 합니다. 파이프라인 라이브러리가 이 미리 준비해 둔 재료입니다.
파이프라인과 컴파일
GPU(Graphics Processing Unit, 그래픽 처리 장치)는 같은 계산을 동시에 아주 많이 돌리는 칩입니다. 화면의 점 수백만 개의 색을 한꺼번에 정하려고 만들었습니다.
GPU 에서 도는 작은 프로그램을 셰이더라고 부릅니다. 하는 일에 따라 셰이더가 여럿으로 나뉩니다.
물체의 모양은 삼각형 여러 개로 짓습니다. 삼각형의 모퉁이를 꼭짓점이라고 부릅니다. 꼭짓점은 물체 쪽의 점이라서 화면의 점과 다른 것입니다. 꼭짓점의 위치를 옮기는 셰이더와 화면의 점마다 색을 정하는 셰이더가 따로 있습니다.
파이프라인은 셰이더 몇 개와 그리기 설정을 한 덩어리로 묶어 굳혀 둔 객체입니다. 색을 어떻게 섞을지, 앞뒤를 어떻게 가릴지 같은 설정이 함께 묶입니다. 그릴 때는 이 덩어리를 고르기만 하면 됩니다.
파이프라인을 만드는 순간 드라이버가 셰이더를 컴파일합니다. 드라이버는 GPU 를 움직이는 소프트웨어입니다. 셰이더를 그 GPU 가 바로 돌리는 기계어로 바꿔 줍니다.
이 컴파일은 사람이 느낄 만큼 걸리기도 합니다. 화면 한 장을 프레임이라고 부릅니다. 그리는 도중에 컴파일하면 그 프레임이 늦게 나옵니다. 사용자 눈에는 화면이 잠깐 멈칫한 것으로 보입니다. 이 현상을 셰이더 컴파일 끊김이라고 부릅니다.
조합이 불어나는 문제
파이프라인은 셰이더와 설정의 조합마다 하나씩 필요합니다. 셰이더 하나만 달라도 다른 파이프라인입니다.
게임이 물체 모양을 다루는 방식을 10가지, 색을 칠하는 방식을 100가지 쓴다고 해 봅니다. 색을 섞는 방식이 3가지면 조합은 10 × 100 × 3 으로 3,000가지까지 늘어납니다. 셰이더 하나는 여러 조합에 되풀이해 들어갑니다.
통째로 만드는 방식에서는 조합마다 셰이더를 다시 컴파일합니다. 같은 색칠 셰이더를 30번 컴파일하는 셈입니다. 로딩 화면에서 3,000가지를 다 만들자니 너무 오래 걸립니다. 그렇다고 필요할 때 만들면 그 순간 화면이 멈칫합니다.
조각을 만들고 잇는 방법
파이프라인 라이브러리는 조합의 각 부분을 따로 컴파일해 두는 길을 엽니다. 위 예라면 모양 처리 10개와 색칠 100개를 따로 만듭니다. 색을 섞는 방식은 셰이더가 아니라 설정이라서 컴파일할 프로그램이 없습니다. 그래서 셰이더 컴파일은 10 + 100 으로 110번까지 줄어듭니다. 조합을 고를 때는 조각을 이어 붙입니다.
Vulkan은 GPU 를 다루는 API(Application Programming Interface, 응용 프로그램 인터페이스) 가운데 하나입니다.
Vulkan 은 기본 기능 위에 선택 기능을 나중에 덧붙여 넓혀 갑니다. 이렇게 덧붙는 선택 기능을 확장이라고 부릅니다.
파이프라인 라이브러리의 바탕이 되는 확장은 VK_KHR_pipeline_library 입니다.
이 확장을 쓰면 파이프라인을 만들 때 VK_PIPELINE_CREATE_LIBRARY_BIT_KHR 표시를 붙일 수 있습니다. 이 표시를 붙인 파이프라인이 라이브러리가 됩니다.
라이브러리는 바인딩할 수 없습니다. 바인딩은 「다음 그리기에 이 파이프라인을 쓴다」고 GPU 에 알리는 일입니다. 라이브러리는 그리기에 쓰는 물건이 아니라 다른 파이프라인의 재료라서 이 일을 못 합니다.
쓸 수 있는 파이프라인을 얻으려면 파이프라인을 하나 더 만듭니다. 이때 VkPipelineLibraryCreateInfoKHR 구조체에 이어 붙일 라이브러리 목록을 담아 넘깁니다.
드라이버는 목록의 조각을 이어 하나의 파이프라인을 내놓습니다. 이 잇는 일을 링크라고 부릅니다.
flowchart TD
subgraph 미리 만든 라이브러리
A["모양 처리 조각"]
B["색칠 조각"]
end
A --> L["링크"]
B --> L
L --> P["바인딩할 수 있는 파이프라인"]
조각은 미리 만들어 둡니다. 필요한 조합이 생기면 링크만 거칩니다. 그림 아래쪽의 파이프라인만 그리기에 씁니다. 그림의 조각은 둘이지만 조각 수는 경우마다 다릅니다. 뒤에 볼 그래픽스 파이프라인은 네 조각으로 나눕니다.
이 짜임은 소스 파일을 하나씩 컴파일해 두고 링커가 마지막에 이어 실행 파일을 만드는 빌드와 닮았습니다. 파일 하나를 고쳐도 그 파일만 다시 컴파일하면 됩니다.
레이 트레이싱 파이프라인의 라이브러리
이 기능은 레이 트레이싱 파이프라인과 함께 Vulkan 에 들어왔습니다. 레이 트레이싱은 광선을 쏘아 맞은 물체를 찾고 그 물체 몫의 셰이더를 돌려 장면을 그리는 방식입니다.
레이 트레이싱 파이프라인은 물체 종류마다 셰이더를 따로 품습니다. 물체가 수백 종이면 셰이더도 수백 개가 한 파이프라인에 들어갑니다. 물체 하나를 더하려고 수백 개를 다시 컴파일하면 낭비입니다. 그래서 셰이더 묶음을 라이브러리로 나눠 두고 마지막에 잇습니다.
조각끼리는 미리 맞춰야 할 값이 있습니다. 광선이 셰이더 사이를 오가며 들고 다니는 데이터를 광선 페이로드라고 부릅니다. 라이브러리마다 이 데이터의 크기 상한을 따로 정하면 이을 때 맞지 않습니다. 그래서 모든 라이브러리가 같은 상한을 적어 둡니다.
그래픽스 파이프라인을 넷으로 나누기
화면에 물체를 그리는 그래픽스 파이프라인은 나중에 확장을 하나 더 얻었습니다. 그 확장의 이름은 VK_EXT_graphics_pipeline_library 입니다.
이 확장은 그래픽스 파이프라인을 네 조각으로 나눕니다.
조각을 보기 전에 낱말 셋을 먼저 풉니다. 첫째는 래스터화입니다. 래스터화는 삼각형 같은 도형을 화면의 점 단위로 바꾸는 단계입니다. 삼각형이 덮는 화면의 점을 골라냅니다.
둘째는 프래그먼트입니다. 래스터화로 골라낸 점 하나하나를 프래그먼트라고 부릅니다. 프래그먼트는 아직 화면에 쓰이기 전의 후보입니다. 더 앞에 있는 물체에 가려지면 버려지기 때문입니다.
셋째는 프래그먼트 셰이더입니다. 프래그먼트의 색을 정하는 셰이더를 이렇게 부릅니다. 앞에서 말한 「화면의 점마다 색을 정하는 셰이더」가 이것입니다.
| 조각 | 무엇을 담나 |
|---|---|
| 정점 입력 | 꼭짓점 데이터를 어떤 꼴로 읽고 어떤 도형으로 묶을지 |
| 래스터화 전 셰이더 | 꼭짓점 위치를 옮기는 셰이더와 래스터화 설정 |
| 프래그먼트 셰이더 | 점의 색을 정하는 셰이더와 앞뒤를 가리는 깊이 검사 설정 |
| 프래그먼트 출력 | 정한 색을 화면에 어떻게 섞어 쓸지 |
네 조각은 그리기가 지나가는 순서와 같습니다. 꼭짓점을 읽고, 위치를 옮기고, 점의 색을 정하고, 화면에 씁니다. 표의 「정점」은 꼭짓점과 같은 말입니다.
앞의 게임 예와 견주면 모양 처리가 래스터화 전 셰이더에 해당합니다. 색칠은 프래그먼트 셰이더, 색 섞는 방식은 프래그먼트 출력에 해당합니다.
나누는 까닭은 바뀌는 빈도가 조각마다 달라서입니다. 앞의 예에서 색칠 방식은 100가지입니다. 모양 처리는 10가지뿐입니다. 자주 바뀌는 조각과 드물게 바뀌는 조각을 따로 들고 있으면 필요한 것만 새로 만들면 됩니다. 네 조각을 다 모아야 쓸 수 있는 파이프라인이 됩니다.
빠른 링크와 최적화 링크
링크에는 두 가지 방식이 있습니다. 아무 표시 없이 이으면 빠른 링크입니다. 드라이버가 지원하면 이 링크는 아주 짧게 끝납니다. 그리는 도중에 해도 화면이 멈칫하지 않을 만큼입니다.
빠른 링크는 조각을 붙이기만 합니다. 조각 사이를 넘나드는 최적화는 하지 않습니다. 예를 들어 앞 조각이 계산해 넘기는 값을 뒤 조각이 안 읽어도 그 계산이 남습니다. 그래서 통째로 만든 파이프라인보다 그릴 때 조금 느릴 수 있습니다.
VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT 표시를 붙이면 최적화 링크가 됩니다. 이을 때 조각 전체를 한 번 더 훑어 다듬습니다.
결과는 통째로 만든 것에 가까워집니다. 대신 시간이 더 걸립니다.
최적화 링크를 하려면 조각을 만들 때부터 준비가 필요합니다. 조각마다 VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT 표시를 붙여야 합니다.
이 표시가 있어야 드라이버가 나중에 다듬는 데 쓸 정보를 조각 안에 남겨 둡니다.
두 방식은 함께 씁니다. 처음 보는 조합이 나오면 그리기를 맡은 스레드가 빠른 링크로 바로 그립니다. 그동안 뒤쪽 스레드가 최적화 링크를 돌리고, 끝나면 바꿔 끼웁니다.
sequenceDiagram
participant 그리기 스레드
participant 드라이버
participant 뒤쪽 스레드
Note over 그리기 스레드: 처음 보는 조합이 필요하다
그리기 스레드->>드라이버: 조각을 빠르게 잇는다
드라이버-->>그리기 스레드: 바로 쓸 파이프라인
Note over 그리기 스레드: 이 파이프라인으로 그린다
그리기 스레드->>뒤쪽 스레드: 같은 조합을 다듬어 달라고 한다
뒤쪽 스레드->>드라이버: 최적화 링크로 잇는다
드라이버-->>뒤쪽 스레드: 다듬은 파이프라인
뒤쪽 스레드-->>그리기 스레드: 다 됐다고 알린다
Note over 그리기 스레드: 다음 프레임부터 다듬은 것으로 그린다
그리기 스레드는 한 번도 오래 기다리지 않습니다. 최적화에 드는 시간은 뒤쪽 스레드가 대신 집니다.
파이프라인 캐시와 견주기
파이프라인 캐시도 컴파일 시간을 줄입니다. 다만 줄이는 방법이 다릅니다.
파이프라인 캐시는 한 번 컴파일한 결과를 남겨 두었다가 같은 것을 다시 만들 때 꺼내 씁니다. 처음 보는 조합에는 소용이 없습니다. 파이프라인 라이브러리는 조합을 조각으로 쪼개 컴파일 자체를 덜 하게 만듭니다. 처음 보는 조합도 조각이 있으면 잇기만 합니다.
둘은 겨루지 않고 함께 씁니다. 조각을 만들 때도 캐시를 넘길 수 있습니다. 그러면 다음 실행에서는 조각 컴파일까지 캐시에서 꺼냅니다.
API 마다 부르는 이름
같은 문제를 GPU 를 다루는 다른 API 도 풉니다. Direct3D 12는 마이크로소프트가 윈도우와 엑스박스용으로 만든 API 입니다. Metal은 애플이 자기 기기용으로 만든 API 입니다. 그런데 이름이 겹쳐서 헷갈리기 쉽습니다.
| API | 이름 | 하는 일 |
|---|---|---|
| Vulkan | VK_KHR_pipeline_library |
조각 파이프라인을 만들고 잇는 바탕. 레이 트레이싱이 먼저 썼다 |
| Vulkan | VK_EXT_graphics_pipeline_library |
그래픽스 파이프라인을 네 조각으로 나눈다 |
| Direct3D 12 | 컬렉션 상태 객체 | 레이 트레이싱 셰이더 묶음을 따로 만들어 두었다가 잇는다 |
| Direct3D 12 | ID3D12PipelineLibrary |
컴파일한 파이프라인을 이름으로 담아 두는 저장소다. 잇는 기능이 아니다 |
| Metal | MTLDynamicLibrary |
셰이더 함수를 따로 컴파일해 두었다가 다른 셰이더가 불러 쓴다 |
Direct3D 12 의 ID3D12PipelineLibrary 는 이름만 같습니다. 하는 일은 위의 파이프라인 캐시에 가깝습니다.
Direct3D 12 에서 Vulkan 의 라이브러리처럼 조각을 잇는 것은 레이 트레이싱의 컬렉션 쪽입니다.
쓸 때와 안 쓸 때
파이프라인 라이브러리는 조합이 많고 어떤 조합이 나올지 미리 다 모르는 프로그램에 맞습니다. Direct3D 게임을 Vulkan 위에서 돌리는 번역 계층과 옛 게임기 에뮬레이터가 대표입니다. 둘 다 그리기 직전에야 어떤 조합이 필요한지 압니다. 끊김 없이 바로 그려야 할 때 빠른 링크가 힘을 씁니다.
조합이 적고 미리 다 알면 쓸 까닭이 적습니다. 로딩 화면에서 한 번에 만들어 두면 됩니다. 한 번에 만든 파이프라인이 그릴 때 드는 시간이 가장 짧습니다.
드라이버가 지원해야 씁니다. 지원하지 않는 기기를 위해 통째로 만드는 길도 함께 남겨 두는 일이 흔합니다. 조각마다 주고받을 값을 맞추는 코드가 필요합니다. 빠르게 이은 파이프라인을 다듬은 것으로 바꾸는 코드도 따로 둬야 합니다. 그만큼 엔진이 복잡해집니다.
이 기능은 대개 게임 엔진과 그래픽스 라이브러리가 안쪽에서 다룹니다.
관련 항목
파이프라인 라이브러리가 조각으로 나누는 파이프라인 객체
파이프라인 · 그래픽스 파이프라인 · 레이 트레이싱 파이프라인 · 컴퓨트 파이프라인 · 파이프라인 상태 객체
그래픽스 파이프라인 라이브러리를 이루는 네 조각
정점 입력 인터페이스 · 래스터화 전 셰이더 · 프래그먼트 셰이더 · 프래그먼트 출력 인터페이스 · 정점 셰이더 · 래스터화
파이프라인 라이브러리를 잇고 다듬는 처리 단계
링크 · 링커 · 링크 타임 최적화 · 셰이더 컴파일 · 컴파일 · 죽은 코드 제거
레이 트레이싱 라이브러리끼리 맞춰야 하는 값과 표
셰이더 그룹 · 광선 페이로드 · 히트 속성 · 셰이더 바인딩 테이블 · 재귀 깊이
파이프라인 라이브러리와 함께 쓰는 Vulkan 객체
파이프라인 캐시 · 파이프라인 레이아웃 · 디스크립터 세트 · 셰이더 모듈 · 특수화 상수
조합이 불어나는 문제를 다른 방법으로 줄이는 수단
동적 상태 · 셰이더 오브젝트 · 우버 셰이더 · 특수화 상수
파이프라인 라이브러리가 줄이려는 지연
셰이더 컴파일 끊김 · 스터터링 · 프레임 · 프레임 시간 · 로딩 시간
파이프라인 라이브러리를 제공하는 그래픽스 API
Vulkan · Direct3D 12 · DirectX Raytracing · Metal
파이프라인 라이브러리와 짜임이 닮은 프로그램 링크 개념
라이브러리 · 정적 라이브러리 · 공유 라이브러리 · 정적 링크 · 동적 링크
파이프라인 라이브러리가 속하는 상위 분류
다른 이름: pipeline library · VK_KHR_pipeline_library · 그래픽스 파이프라인 라이브러리 · graphics pipeline library · VK_EXT_graphics_pipeline_library