파이프라인
셰이더는 혼자 돌아가지 않습니다. GPU 파이프라인의 정해진 자리 가운데 한 곳에 놓이고, 그 자리가 진입점이 무엇을 건네받고 무엇을 돌려주어야 하는지 결정합니다. 이 페이지는 TypeShade가 코드를 생성하는 자리들을 설명하고, 문법은 언어 가이드에 맡깁니다.
스테이지
각 행은 호스트가 구성한 파이프라인의 스테이지 하나입니다.
| 스테이지 | 건네받는 것 | 만들어 내는 것 |
|---|---|---|
| 버텍스 | 그릴 때의 버텍스 하나입니다. 그 버텍스의 번호와, 호스트가 버텍스 버퍼에 배치해 둔 필드를 받습니다. | 클립 공간 위치와, 프래그먼트 스테이지가 읽을 값을 만들어 냅니다. |
| 프래그먼트 | 버텍스 스테이지가 만든 값을 이 프래그먼트에 맞게 가중한 결과와, 프래그먼트 자신의 위치를 받습니다. | 파이프라인이 선언한 색상 어태치먼트마다 값 하나를 만들어 냅니다. |
| 컴퓨트 | 디스패치 격자 안의 자기 좌표와, 호스트가 바인딩해 둔 리소스를 받습니다. | 돌려주는 값은 없습니다. 컴퓨트 진입점은 들고 있는 리소스에 씁니다. |
진입점
진입점의 시그니처는 그 스테이지 인터페이스를 그대로 적어 둔 것입니다. @builtin(...) 매개변수는 스테이지가 인보케이션에게 건네는 값이며, 버텍스 번호나 프래그먼트 위치가 여기에 해당합니다. 구조체 매개변수는 버텍스 스테이지에서는 버텍스마다의 입력이고, 프래그먼트 스테이지에서는 보간된 값입니다. 반환 타입은 스테이지가 파이프라인에 돌려주는 값이라서, 버텍스 진입점은 위치를 돌려주고 프래그먼트 진입점은 색을 돌려줍니다. 데코레이터와 표기는 셰이더 스테이지에 있고, 이 페이지는 의미만 다룹니다.
class VsIn { @location(0) position: vec3 @location(1) uv: vec2}
@vertexexport function vs(@builtin("vertex_index") vid: u32, vin: VsIn): vec4 { return camera.view * vec4(vin.position, 1)}
@fragmentexport function fs(@builtin("position") pid: vec4): vec4 { return vec4(pid.x, 0, 0, 1)}그래서 시그니처 한 줄만 읽어도 그 함수가 어느 스테이지에 속하는지, 파이프라인이 무엇을 공급해야 하는지, 파이프라인이 무엇을 돌려받는지 알 수 있습니다.
보간
버텍스 스테이지와 프래그먼트 스테이지 사이에서 래스터라이저가 프리미티브가 덮는 프래그먼트를 가려냅니다. 그리고 프래그먼트마다, 버텍스들이 만든 값을 각 버텍스에서 얼마나 가까운지에 따라 가중해 프래그먼트 스테이지에 건넵니다. 이 과정을 보간이라고 합니다. 버텍스 진입점은 버텍스마다 값을 쓰고 프래그먼트 진입점은 프래그먼트마다 값을 읽으므로, 이름은 같아도 서로 다른 값입니다.
class VsOut { @builtin("position") pos: vec4 @location(0) uv: vec2 @location(1) id: u32 @location(2) @interpolate("flat") tint: vec3}실수 @location 필드는 @interpolate로 다른 방식을 지정하지 않는 한 이렇게 가중됩니다. 정수는 가중할 수 없으므로 컴파일러는 따로 적지 않아도 두 타깃 모두 id에 flat 한정자를 붙이고, 프래그먼트는 프리미티브의 버텍스 하나가 낸 값을 그대로 읽습니다. tint에 적은 @interpolate("flat")도 같은 처리를 요청합니다. 필드 데코레이터는 타입이 적어 두었습니다.
컴퓨트
컴퓨트 스테이지 앞에는 래스터라이저가 없고 뒤에는 어태치먼트가 없습니다. 호스트가 작업 항목의 격자를 디스패치하면, 진입점은 그 격자 안의 자기 좌표를 builtin 매개변수로 읽고, 만들어 낸 값은 모두 스토리지 리소스를 거쳐 나갑니다. 쓰기 가능한 리소스를 어떻게 선언하는지는 리소스가 적어 두었습니다.
declare const pixels: storage<array<f32>, "read_write">
@compute([64, 1, 1])export function paint(@builtin("global_invocation_id") gid: vec3u) { pixels[gid.x] = camera.pos.x}더 읽을 자료
- WebGPU 명세가 이 스테이지들이 속한 렌더 파이프라인과 컴퓨트 파이프라인을 정의합니다.
- MDN GPURenderPipeline과 MDN GPUComputePassEncoder에서 그 파이프라인을 움직이는 호스트 코드를 볼 수 있습니다.
이 경로의 다음 글은 WebGPU와 WebGL2입니다. 호스트 애플리케이션과 컴파일러가 일을 어떻게 나누는지 다룹니다.