함수
이 페이지에서

Functions: TypeScript 함수에서 GPU 함수로

TypeShade에서 함수는 계산을 이름 붙이고 입력과 출력을 명확하게 만드는 기본 단위입니다. TypeScript에서 함수를 읽는 방법을 그대로 출발점으로 삼되, "use typeshade" 파일에서는 함수가 GPU IR로 내려갈 수 있는 계산이어야 한다는 차이를 이해해야 합니다.

1. 함수의 구조

함수는 이름, parameter 목록, return type, body로 구성됩니다. TypeShade는 이 익숙한 구조를 유지하면서 parameter와 return type에 GPU 의미를 부여합니다.

function addBias(value: f32): f32 {
return value + 0.5
}

여기서 value는 입력 parameter이고 f32는 입력과 결과의 GPU 타입입니다. return은 함수가 계산한 값을 호출자에게 돌려줍니다.

2. Parameter와 return type

parameter는 함수가 읽는 입력이고 return type은 계산 결과의 GPU value shape를 설명합니다. 값 매개변수를 다시 대입하면 해당 호출의 지역 복사본이 바뀌고 호출자의 값은 유지됩니다. helper, 메서드와 stage 입력에 모두 적용됩니다. 타입은 컴파일러가 표현식의 유효성을 판단하는 정보입니다.

function mix(a: vec4, b: vec4, amount: f32): vec4 {
return a + (b - a) * amount
}
부분역할
a, bGPU 입력 값입니다.
amount: f32스칼라 입력이며 컴파일러가 연산 타입을 확인합니다.
: vec4호출자에게 반환할 GPU 값의 shape입니다.

3. Helper function

stage decorator가 없는 top-level function은 다른 shader 함수에서 호출할 수 있는 helper입니다. 반복되는 계산을 이름 있는 함수로 분리하면 shader를 읽고 검증하기 쉬워집니다.

function saturate(x: f32): f32 {
return clamp(x, 0.0, 1.0)
}
function remap(x: f32): f32 {
return saturate(x * 2.0 - 1.0)
}

helper는 pipeline entry가 아니므로 stage decorator가 없습니다. 이 구조는 계산을 작은 단위로 나누고 entry의 역할을 읽기 쉽게 만듭니다.

4. 함수 호출

함수 호출은 일반적인 TypeScript 호출처럼 보이지만 호출 대상과 argument는 TypeShade의 GPU 타입 체계에 속해야 합니다. 일반 JavaScript API를 호출한다고 생각하면 안 됩니다.

function addBias(value: f32): f32 {
return value + 0.5
}
function shade(value: f32): f32 {
const biased = addBias(value)
return clamp(biased, 0.0, 1.0)
}

호출은 일반 함수 호출처럼 보이지만, addBias의 입력과 반환값은 TypeShade가 이해하는 GPU 타입이어야 합니다.

5. Shader entry function

pipeline이 실행을 시작하는 함수는 top-level export function에 stage decorator를 붙여 선언합니다. @compute, @vertex, @fragment가 각각 GPU 실행 단계와 연결됩니다.

@compute([64, 1, 1])
export function paint(@builtin("global_invocation_id") gid: vec3u) {
pixels[gid.x] = addBias(camera.pos.x)
}

export는 TypeScript module 개념과 함께 entry를 compiler가 발견할 수 있게 합니다. stage decorator는 그 exported function이 어느 pipeline stage인지 추가로 설명합니다.

6. Builtin은 parameter로 받습니다

GPU가 제공하는 stage input은 숨겨진 전역 변수가 아니라 함수 signature에 명시합니다. 이렇게 하면 함수의 입력이 코드만 읽어도 드러나고 helper와 entry의 경계도 분명해집니다.

@compute([64, 1, 1])
export function paint(
@builtin("global_invocation_id") gid: vec3u
) {
const i = gid.x
pixels[i] = pixels[i] + camera.pos.x
}
표현의미
@builtin("global_invocation_id")GPU가 제공하는 compute 입력을 지정합니다.
gid: vec3u그 입력의 TypeShade 타입과 local 이름입니다.
gid.x현재 invocation의 x component를 읽습니다.

7. Compute entry를 읽는 법

@compute([64, 1, 1])는 workgroup 크기를 선언하고, gid는 global_invocation_id builtin을 받는 parameter입니다. gid.x를 통해 현재 invocation의 x 좌표를 읽습니다.

@compute([64, 1, 1])
export function paint(@builtin("global_invocation_id") gid: vec3u) {
const i = gid.x
pixels[i] = pixels[i] + camera.pos.x
}

이 함수를 읽을 때는 먼저 stage와 workgroup 크기를 보고, 다음으로 parameter를 통해 어떤 GPU 입력이 들어오는지 확인한 뒤 body에서 계산을 따라가면 됩니다.

8. Vertex와 fragment entry

vertex와 fragment도 같은 함수 모델을 사용합니다. stage decorator가 실행 단계를 결정하고 parameter와 return type이 pipeline interface를 설명합니다.

@vertex
export function vs(
@builtin("vertex_index") vid: u32,
vin: VsIn
): vec4 {
return camera.view * vec4(vin.position, 1)
}
@fragment
export function fs(@builtin("position") pid: vec4): vec4 {
return vec4(pid.x, 0, 0, 1)
}

vertex의 vid와 vin은 서로 다른 입력입니다. 하나는 GPU builtin이고 다른 하나는 사용자 정의 struct 입력입니다. fragment의 pid 역시 signature에 명시되어 있으므로 함수만 읽어도 필요한 입력을 알 수 있습니다.

9. 함수와 scope

지역 변수는 함수 호출마다 생깁니다. 매개변수와 함수 본문의 지역 변수는 모듈 값과 같은 이름을 쓸 수 있으며, 클로저는 가장 가까운 선언을 찾습니다. 같은 스코프의 중복 선언은 여전히 오류입니다. 변수와 매개변수는 `target`처럼 TypeScript에서 유효한 이름을 쓸 수 있으며, 셰이더 작성기가 백엔드의 예약 이름을 바꿔 출력합니다. 리소스, 진입점과 구조체 필드는 기존 인터페이스 이름 규칙을 따릅니다.

function exposure(color: vec3, amount: f32): vec3 {
const factor = amount + 1.0
return color * factor
}

factor는 함수 안에서만 존재하는 local value입니다. 반대로 camera나 pixels 같은 resource는 host와 연결된 shader interface이며, builtin parameter는 GPU stage가 제공하는 입력입니다.

10. TypeScript 함수와의 차이

  • 임의의 JavaScript runtime API를 호출하지 않습니다.
  • 동적 객체 생성과 일반적인 runtime side effect를 shader 계산으로 가정하지 않습니다.
  • entry point는 class method가 아니라 top-level exported function입니다.
  • builtin은 implicit global이 아니라 명시적인 parameter입니다.
  • parameter와 return type은 GPU value semantics에 맞아야 합니다.

11. 작은 함수에서 실제 entry까지

아래 예제는 helper가 계산을 담당하고 entry가 builtin과 resource를 연결하는 전형적인 구조입니다.

"use typeshade";
function addBias(value: f32): f32 {
return value + 0.5;
}
@compute([64, 1, 1])
export function paint(@builtin("global_invocation_id") gid: vec3u) {
const value = camera.pos.x;
pixels[gid.x] = addBias(value);
}

이 예제의 핵심은 함수 자체보다 경계입니다. addBias는 재사용 가능한 계산이고, paint는 stage와 builtin을 선언하면서 실제 GPU invocation과 resource를 연결합니다.

다음: Control flow

이 페이지 편집 문제 보고