사전 파이프라인 레이아웃
개념

파이프라인 레이아웃

gabury1고친 사람 github-actions[bot]

파이프라인 레이아웃은 그래픽 카드에서 도는 프로그램이 받을 데이터가 어느 칸으로 들어오는지를 파이프라인에 미리 알려 줍니다. 파이프라인은 그 프로그램과 그리기 설정을 미리 묶어 굳혀 둔 것입니다. 레이아웃은 몇 번 묶음의 몇 번 칸에 어떤 종류의 데이터가 오는지를 목록 하나로 정해 둡니다. 그리기 직전에 끼우는 데이터도 이 목록을 따라야 합니다.

쉽고 빠른 이해

파이프라인은 그래픽 카드에서 도는 프로그램과 그리기 설정을 미리 묶어 굳혀 둔 것입니다. 파이프라인 레이아웃은 이 프로그램과 프로그램이 읽을 데이터 사이의 약속입니다.

데이터는 여러 개를 한 벌로 묶어 건넵니다. 이 한 벌을 묶음이라고 하겠습니다. 의자를 그린다면 「0번 묶음은 카메라, 1번 묶음은 재질, 2번 묶음은 의자 위치」라고 정해 둡니다.

이 약속은 프로그램을 그래픽 카드의 기계어로 옮기기 전에 있어야 합니다. 옮기는 동안 「1번 묶음의 0번 칸」을 찾아가는 길이 기계어 안에 박히기 때문입니다. 약속을 미리 받아 두면 그릴 때는 묶음을 끼우기만 하면 됩니다.

어떻게 도나:

  1. 묶음마다 칸의 종류를 적은 틀을 만듭니다
  2. 틀들을 번호 순서로 늘어놓아 파이프라인 레이아웃을 만듭니다
  3. 파이프라인을 만들 때 이 레이아웃을 함께 넘깁니다
  4. 그릴 때는 같은 틀로 만든 묶음을 그 번호에 끼웁니다

대가는 약속을 바꾸기 어렵다는 것입니다. 칸 배치가 달라지면 레이아웃도 파이프라인도 새로 만들어야 합니다. 파이프라인마다 약속이 다르면 파이프라인을 바꿀 때 끼워 둔 묶음을 다시 끼웁니다.

직접 만들 일은 드뭅니다. 보통은 게임 엔진이나 그래픽 라이브러리가 안쪽에서 만듭니다. Vulkan 이나 WebGPU 를 직접 다룰 때 만납니다. WebGPU 에서는 파이프라인이 하나뿐이면 레이아웃을 알아서 만들게 맡겨도 됩니다.

상세

연극 무대 뒤에는 소품 선반이 있습니다. 소품 담당은 첫 연습 전에 선반 몇째 칸에 무엇을 둘지 표로 정해 배우에게 줍니다. 배우는 그 표를 외운 채로 연습합니다. 공연 날마다 소품은 새것으로 바뀌어도 칸 배치는 같아서 배우는 보지 않고 손을 뻗습니다.

소품 담당이 준 표가 파이프라인 레이아웃입니다. 배우는 데이터를 읽는 프로그램입니다. 날마다 바뀌는 소품은 그 프로그램이 읽는 데이터입니다.

이 절은 세 가지를 봅니다. 레이아웃에 무엇을 적나, 왜 파이프라인을 만들 때 정하나, 여러 파이프라인이 어떻게 나눠 쓰나입니다. 의자 하나를 화면에 그리는 장면을 줄곧 예로 씁니다. 끝에서는 코드 한 조각과 다른 그래픽스 라이브러리가 쓰는 이름을 봅니다.

셰이더가 데이터를 찾는 번호

그래픽 카드의 프로그램은 읽을 데이터를 이름이 아니라 번호 둘로 가리킵니다. 그 번호가 무엇인지를 의자 표면을 칠하는 프로그램으로 풉니다. 필요한 낱말부터 차례로 짚습니다.

GPU(Graphics Processing Unit, 그래픽 처리 장치)는 같은 계산을 동시에 아주 많이 돌리는 칩입니다. 화면의 점 수백만 개의 색을 한꺼번에 정하려고 만들었습니다. 이 칩을 얹은 판이 그래픽 카드입니다.

셰이더는 GPU 위에서 도는 작은 프로그램입니다. 의자 표면의 점 하나가 무슨 색이 될지 계산하는 코드가 셰이더입니다. 이 코드는 수많은 점에서 동시에 돕니다.

셰이더는 계산에 쓸 데이터를 바깥에서 받아 읽습니다. 이 데이터가 자원입니다. 의자라면 나뭇결 그림, 광택 값, 의자가 놓인 위치, 카메라 위치가 자원입니다.

셰이더 코드에는 자원의 이름 대신 번호가 적힙니다. 「1번 묶음의 0번 칸」처럼 번호 둘로 자원을 가리킵니다. 그리기 전에 그 번호에 진짜 자원을 끼워 두면 셰이더가 번호를 따라가 읽습니다.

자원 여러 개를 한 벌로 묶어 한 번에 끼우는 묶음을 디스크립터 세트라고 부릅니다. 자원을 하나씩 끼우는 것보다 명령이 적게 듭니다. 이 문서도 앞으로 묶음을 세트라고 씁니다.

번호 둘에도 이름이 있습니다. 묶음 번호는 세트 번호, 칸 번호는 바인딩 번호입니다.

세트의 틀을 늘어놓은 목록

세트를 만들기 전에 틀부터 정합니다. 몇 번 칸에 어떤 종류의 자원이 오는지 적은 틀이 디스크립터 세트 레이아웃입니다. 의자의 재질 세트 틀에는 「0번 칸은 그림, 1번 칸은 그림을 읽는 규칙, 2번 칸은 값을 담은 버퍼」가 적힙니다.

파이프라인 레이아웃은 이 틀들을 세트 번호 순서로 늘어놓은 목록입니다. 목록에서 몇 번째에 놓였는지가 곧 세트 번호입니다. 의자 예에서는 카메라 틀, 재질 틀, 물체 틀이 0번, 1번, 2번으로 놓입니다.

목록에는 틀 말고 하나가 더 들어갈 수 있습니다. 푸시 상수는 세트를 거치지 않고 그리기 명령에 바로 실어 보내는 작은 값입니다. 값 하나를 보내려고 세트를 새로 만들지 않아도 됩니다.

레이아웃에는 이 값이 몇 바이트인지와 어느 셰이더가 읽는지를 적습니다. 이 내용을 푸시 상수 범위라고 부릅니다.

목록에 드는 것 무엇을 적나 의자 예
0번 세트 틀 카메라 세트의 칸 배치 0번 칸에 카메라 값 버퍼
1번 세트 틀 재질 세트의 칸 배치 나뭇결 그림 · 그림을 읽는 규칙 · 광택 값 버퍼
2번 세트 틀 물체 세트의 칸 배치 0번 칸에 의자 위치 버퍼
푸시 상수 범위 명령에 실을 값의 크기 의자 색 값 하나

표에는 틀만 있습니다. 진짜 자원은 없습니다. 나뭇결 그림이 메모리 어디에 있는지는 레이아웃이 모릅니다. 레이아웃은 「1번 세트의 0번 칸에는 그림이 온다」까지만 정합니다.

flowchart TD
    P["파이프라인"]
    subgraph PL["파이프라인 레이아웃"]
        L0["0번 · 카메라 틀"]
        L1["1번 · 재질 틀"]
        L2["2번 · 물체 틀"]
        PC["푸시 상수 범위"]
        L0 ~~~ L1 ~~~ L2 ~~~ PC
    end
    PL -->|파이프라인을 만들 때 넘김| P
    L1 -->|이 틀로 찍어 냄| A["의자 재질 세트"]
    L1 -->|이 틀로 찍어 냄| B["책상 재질 세트"]

그림에서 레이아웃 안에는 틀만 들어 있습니다. 진짜 세트는 그 바깥에서 틀로 찍혀 나옵니다.

의자 재질 세트와 책상 재질 세트는 같은 1번 틀로 만들었습니다. 그래서 둘 다 1번 세트로 끼울 수 있습니다. 다른 것은 칸에 든 자원뿐입니다.

파이프라인을 만들 때 정하는 까닭

레이아웃은 그릴 때가 아니라 파이프라인을 만들 때 넘깁니다. 그 까닭을 셰이더가 기계어로 옮겨지는 과정에서 찾습니다.

파이프라인은 셰이더와 그리기 설정을 그리기 전에 한데 묶어 굳혀 둔 객체입니다. 그릴 때마다 설정을 하나씩 맞추지 않고 묶음 하나를 골라 쓰려고 만듭니다.

파이프라인을 만들 때 셰이더 코드가 GPU 의 기계어로 옮겨집니다. 이 일이 컴파일입니다. 컴파일은 드라이버가 맡습니다.

드라이버는 운영체제와 GPU 사이에서 프로그램의 명령을 칩이 알아듣는 꼴로 바꿔 주는 소프트웨어입니다.

드라이버는 컴파일하면서 「1번 세트의 0번 칸」을 찾아가는 길을 기계어에 적어 넣습니다. 적히는 것은 자원의 주소가 아닙니다. 세트 안에서 그 칸을 찾아가는 방법입니다. 진짜 자원은 그릴 때 끼운 세트에서 옵니다.

길을 적으려면 세트가 몇 개이고 칸마다 무슨 종류가 오는지를 컴파일 때 알아야 합니다. 파이프라인 레이아웃이 그 정보를 줍니다. 그래서 파이프라인을 만들 때 레이아웃을 함께 넘깁니다.

찾는 길이 기계어에 미리 박혀 있으니 그릴 때 할 일이 줄어듭니다. 그리기 직전에는 세트를 끼우는 명령만 내면 됩니다. 칸 배치를 그릴 때마다 다시 따지지 않습니다. 표를 외운 배우가 보지 않고 손을 뻗는 것과 같습니다.

셰이더가 읽는 칸은 레이아웃에 모두 적혀 있어야 합니다. 반대로 레이아웃에는 셰이더가 안 읽는 칸이 더 있어도 됩니다. 이 차이 덕분에 셰이더가 다른 파이프라인 여럿이 레이아웃 하나를 같이 쓸 수 있습니다.

파이프라인을 바꿔도 남는 세트

한 장면은 파이프라인 하나로 끝나지 않습니다. 나무 의자와 유리컵은 칠하는 셰이더가 달라서 파이프라인도 따로 둡니다. 파이프라인을 바꿀 때 끼워 둔 세트가 언제 남는지가 이 소절의 주제입니다.

두 파이프라인의 레이아웃이 0번부터 N번까지 같은 틀을 가지면, 그 번호까지 끼워 둔 세트는 파이프라인을 바꿔도 남습니다. 푸시 상수 범위도 같아야 합니다. 이때 두 레이아웃이 N번 세트까지 호환된다고 말합니다.

앞 번호에서 틀이 하나라도 다르면 그 번호부터 뒤는 모두 다시 끼웁니다. 뒤 번호의 틀이 같아도 그렇습니다. 아래 표는 의자에서 유리컵으로 넘어갈 때를 보입니다. 유리컵은 재질 틀에 빛이 꺾이는 정도를 담는 칸이 더 있어서 1번 틀이 다릅니다.

세트 번호 나무 의자 파이프라인 유리컵 파이프라인 바꾼 뒤
0 카메라 틀 카메라 틀 남는다
1 나무 재질 틀 유리 재질 틀 다시 끼운다
2 물체 틀 물체 틀 다시 끼운다

표의 2번이 이 규칙의 핵심입니다. 두 파이프라인의 2번 틀은 같습니다. 그런데 앞의 1번이 달라서 2번 세트도 다시 끼워야 합니다.

그래서 세트 번호는 바뀌는 빈도로 매깁니다. 화면 한 장을 그려 내는 한 차례를 프레임이라고 합니다. 한 프레임 내내 안 바뀌는 카메라를 0번에, 물체마다 바뀌는 위치를 끝 번호에 둡니다. 이렇게 두면 파이프라인을 바꿀 때 잃는 세트가 뒤쪽 몇 개로 줄어듭니다.

아예 레이아웃 하나를 여러 파이프라인이 같이 쓰게 만들기도 합니다. 앞 소절에서 본 대로 레이아웃에 안 쓰는 칸이 있어도 되므로 가능합니다. 그러면 파이프라인을 바꿔도 끼워 둔 세트가 모두 남습니다.

WebGPU 코드로 본 모습

프로그램이 GPU 를 다룰 때 부르는 함수 모음이 그래픽스 API(Application Programming Interface, 응용 프로그램 인터페이스)입니다. 파이프라인 레이아웃도 이 함수로 만듭니다.

WebGPU는 웹 브라우저에서 GPU 를 다루는 API 입니다. 여기서는 WebGPU 로 레이아웃을 만들어 봅니다.

WebGPU 는 세트를 바인드 그룹, 세트의 틀을 바인드 그룹 레이아웃이라고 부릅니다. 파이프라인 레이아웃은 이름이 같습니다. 아래 코드의 세 틀은 앞의 표와 같은 카메라, 재질, 물체 틀입니다.

JavaScript
const layout = device.createPipelineLayout({
  bindGroupLayouts: [
    cameraLayout,    // 0번 그룹 · 카메라
    materialLayout,  // 1번 그룹 · 재질
    objectLayout,    // 2번 그룹 · 물체
  ],
});

device 는 프로그램이 GPU 에 명령을 내리려고 얻은 객체입니다. createPipelineLayout 이 파이프라인 레이아웃을 만듭니다. 배열에 넣은 순서가 곧 그룹 번호입니다. WebGPU 에는 푸시 상수가 없어서 목록에 틀만 들어갑니다.

이렇게 만든 layout 을 파이프라인을 만드는 함수에 넘깁니다. 그리는 파이프라인이든 계산하는 파이프라인이든 넘기는 방법은 같습니다.

WebGPU 에서는 레이아웃을 손으로 만들지 않고 layout: "auto" 라고 적어도 됩니다. 셰이더 코드에 적힌 번호를 읽어 레이아웃을 알아서 만들라는 뜻입니다. 이렇게 만든 레이아웃은 그 파이프라인 전용입니다. 그 틀로 찍어 낸 바인드 그룹은 다른 파이프라인에 끼울 수 없습니다.

그래픽스 API 마다 부르는 이름

같은 역할을 하는 목록이 그래픽스 API 마다 있습니다. 이름과 목록에 드는 것이 조금씩 다릅니다.

API 이 목록을 부르는 이름 목록에 드는 것
Vulkan 파이프라인 레이아웃 디스크립터 세트 레이아웃 · 푸시 상수 범위
WebGPU 파이프라인 레이아웃 바인드 그룹 레이아웃
Direct3D 12 루트 시그니처 디스크립터 테이블 · 루트 상수
Metal 따로 만드는 객체가 없다 셰이더 코드에 적은 인자 번호
OpenGL 없다 없다

Direct3D 12 에서는 루트 시그니처가 이 목록입니다. 디스크립터 테이블은 세트 틀에, 루트 상수는 푸시 상수에 해당합니다.

Metal 에는 따로 만드는 목록 객체가 없습니다. 약속은 셰이더 코드에 적은 인자 번호에 들어 있습니다.

OpenGL 에는 이 목록이 없습니다. 번호가 붙은 연결 지점에 자원을 하나씩 꽂습니다. 셰이더와 꽂힌 자원이 맞는지는 그릴 때 드라이버가 따집니다.

Vulkan 과 WebGPU 와 Direct3D 12 는 이 따지는 일을 파이프라인을 만들 때로 앞당겼습니다. 그 앞당긴 약속을 담는 객체가 파이프라인 레이아웃과 루트 시그니처입니다.

쓸 때와 안 쓸 때

백엔드 개발자가 파이프라인 레이아웃을 손으로 만들 일은 드뭅니다. 게임 엔진이나 그래픽 라이브러리가 안쪽에서 만듭니다. 직접 만나는 때는 Vulkan 이나 WebGPU 를 직접 다룰 때입니다.

GPU 로 계산만 돌릴 때도 만납니다. 계산용 셰이더를 묶어 둔 컴퓨트 파이프라인도 레이아웃을 받습니다. 두 값 목록을 더하는 계산이라면 「0번 세트에 입력 버퍼 둘과 결과 버퍼 하나」를 적은 레이아웃을 씁니다.

파이프라인이 하나뿐인 작은 프로그램이면 WebGPU 의 "auto" 로 충분합니다. 나눠 쓸 다른 파이프라인이 없으니 전용이어도 잃는 것이 없습니다.

파이프라인이 여럿이고 같은 세트를 오가며 쓰면 레이아웃을 손으로 만듭니다. 틀을 직접 만들어 여러 파이프라인이 같이 쓰게 해야 파이프라인을 바꿔도 세트가 남습니다.

관련 항목

파이프라인 레이아웃을 이루는 구성 요소

디스크립터 세트 레이아웃 · 바인드 그룹 레이아웃 · 푸시 상수 · 푸시 상수 범위 · 바인딩 · 디스크립터 타입

파이프라인 레이아웃의 틀로 찍어 끼우는 묶음

디스크립터 세트 · 바인드 그룹 · 디스크립터 · 디스크립터 풀 · 자원

파이프라인 레이아웃을 받아 굳는 파이프라인 객체

파이프라인 · 그래픽스 파이프라인 · 컴퓨트 파이프라인 · 렌더 파이프라인 · 레이 트레이싱 파이프라인 · 파이프라인 캐시

파이프라인 레이아웃의 칸이 가리키는 자원

텍스처 · 샘플러 · 버퍼 · 유니폼 버퍼 · 스토리지 버퍼 · 이미지 뷰

파이프라인 레이아웃에 맞춰 컴파일되는 셰이더

셰이더 · 셰이더 모듈 · 정점 셰이더 · 프래그먼트 셰이더 · 컴퓨트 셰이더 · SPIR-V · WGSL · GLSL

파이프라인 레이아웃을 다른 API 에서 대신하는 객체

루트 시그니처 · 디스크립터 테이블 · 루트 상수 · 아규먼트 버퍼

파이프라인 레이아웃의 세트 대신 자원을 건네는 방식

바인드리스 · 디스크립터 인덱싱 · 푸시 디스크립터 · 디스크립터 버퍼

파이프라인 레이아웃을 정의한 그래픽스 API

Vulkan · WebGPU · Direct3D · Metal · OpenGL · 그래픽스 API

파이프라인 레이아웃이 속하는 상위 분류

그래픽스 · GPU · API · 디바이스 드라이버 · 렌더링 · 프레임

다른 이름: pipeline layout · VkPipelineLayout · GPUPipelineLayout