TypeScript로 쓰는 셰이더 언어, TypeShade

TypeShade는 TypeScript의 문법과 타입, 편집기를 그대로 쓰는 독립된 언어입니다. "use typeshade"로 시작하는 파일 하나가 WebGPU용 WGSL과 WebGL2용 GLSL ES 3.00을 냅니다. 저장소의 예제 98개 가운데 73개가 파일 하나로 둘 다 냅니다.

Playground Quick Start 왜 TypeShade인가

first.shade.ts
first.shade.ts
"use typeshade"
class Uniforms {
time: f32
mouse: vec2
warp: f32
ink: vec3
paper: vec3
}
declare const u: uniform<Uniforms>
@fragment
export function main(@location(0) uv: vec2): vec4 {
const p = uv * 4.0 - u.mouse * 2.0
const a = sin(p.x + u.time * 0.5) + cos(p.y - u.time * 0.3)
const b = sin(p.y * u.warp + a) * cos(p.x * u.warp - a)
const t = b * 0.5 + 0.5
const s = t * t * (3.0 - 2.0 * t)
return vec4(mix(u.ink, u.paper, s * s * (3.0 - 2.0 * s)), 1.0)
}
TypeScript로 쓰고 TypeShade가 컴파일한 굽은 띠 셰이더. 빌드할 때 렌더링함.

페이지가 매 프레임 채우는 값이라 컨트롤이 없습니다: time, mouse. mouse는 캔버스 왼쪽 아래를 원점으로 0에서 1까지 가며, uv와 같은 공간입니다.

왼쪽 파일을 오른쪽에 그립니다. 한 줄을 고치거나 컨트롤을 움직이면 그림이 따라옵니다.

파일 하나, 출력 둘

컴파일러는 hello.shade.ts를 하나의 중간 표현으로 변환한 뒤 두 타깃의 코드를 생성합니다. 이 파일은 다른 파일을 가져오지 않으므로 그 자체가 프로그램 전부이고, 옆 창에는 호스트가 WebGPU나 WebGL2에 넘기는 WGSL과 GLSL ES 3.00 프래그먼트가 탭 하나씩으로 들어 있습니다.

hello.shade.ts

"use typeshade";
class Clip {
@builtin("position") pos: vec4;
}
class Color {
@location(0) color: vec4;
}
@vertex
export function vs(@builtin("vertex_index") i: u32): Clip {
const x = i === 1 ? 0.8 : i === 2 ? 0. : -0.8;
const y = i === 2 ? 0.8 : -0.8;
return { pos: vec4(x, y, 0., 1.) };
}
@fragment
export function fs(): Color {
return { color: vec4(1., 0., 0., 1.) };
}
struct Clip {
@builtin(position) pos: vec4<f32>,
}
struct Color {
@location(0) color: vec4<f32>,
}
@vertex
fn vs(@builtin(vertex_index) i: u32) -> Clip {
let _cse0 = (i == 2u);
let x = select(select(-0.8, 0.0, _cse0), 0.8, (i == 1u));
let y = select(-0.8, 0.8, _cse0);
return Clip(vec4<f32>(x, y, 0.0, 1.0));
}
@fragment
fn fs() -> Color {
return Color(vec4<f32>(1.0, 0.0, 0.0, 1.0));
}
#version 300 es
precision highp float;
precision highp int;
layout(location = 0) out vec4 color;
void main() {
color = vec4(1.0, 0.0, 0.0, 1.0);
}

더 많은 셰이더

예제 전체

TypeScript에서 그대로 이어지는 것

GPU 코드에는 리소스, 값 레이아웃, 엔트리 포인트 세 가지가 있습니다. TypeShade는 각각에 TypeScript가 이미 가진 자리를 주고, 나머지 언어는 GPU에 맞는 범위에서 그대로 둡니다. 전체 규칙은 언어 가이드에 있습니다.

"use typeshade"
이 지시어가 언어의 경계입니다

파일의 첫 문장, JavaScript 지시어가 놓이는 자리에 씁니다. 이 줄이 없는 파일은 셰이더로 컴파일되지 않고, 있는 파일은 그 아래 모든 코드가 TypeShade로 검사되어 컴파일러의 중간 표현으로 내려갑니다.

f32, vec3, mat4, sin(x)
GPU 타입과 builtin은 전역입니다

f32, vec3, mat4와 sin, vec4(...) 같은 builtin은 import 없이 씁니다. Math.sin과 Math.PI는 같은 연산의 별칭입니다. 검사기는 코드를 내기 전에 편집기 안에서 셰이더 규칙을 적용합니다.

class VsIn { @location(0) uv: vec2 }
type이나 class가 값 레이아웃입니다

메타데이터가 없는 데이터는 type 별칭으로 쓰고, 필드마다 @location이나 @builtin이 필요하면 class로 씁니다. 클래스의 필드가 두 타깃이 받는 구조체의 레이아웃이 됩니다.

new Circle(center, 0.3).coverage(p)
클래스는 TypeScript 클래스 그대로입니다

필드, 생성자와 new, 메서드와 static 함수, super와 abstract가 있는 extends, 제네릭 클래스와 함수, 믹스인 패턴까지 컴파일됩니다. 메서드는 구조체를 첫 매개변수로 받는 함수로 내려가고, 제네릭은 타입 인자 조합마다 한 번씩 컴파일됩니다. 예제 페이지에 각각의 파일이 있습니다.

declare const u: uniform<Camera>
declare가 호스트가 채우는 리소스를 선언합니다

uniform<T>는 유니폼 블록을 읽고, storage<T, "read_write">는 셰이더가 쓰는 스토리지 버퍼이입니다. 초기값은 없습니다. 슬롯은 호스트의 것이고 파일의 선언 순서를 따르며, reflect()가 그 레이아웃을 알려 줍니다.

@fragment export function fs(v: VsOut): vec4
데코레이터가 붙은 export가 엔트리 포인트입니다

@vertex, @fragment, @compute([64, 1, 1])가 스테이지를 정하고, 없는 함수는 헬퍼입니다. 스테이지 입력은 @builtin("vertex_index")나 @location 같은 명시적 매개변수로 받습니다. 숨은 전역 변수는 없습니다.

await scale({ k, xs, ys }, 4)
호스트 파일이 모듈을 가져와 부릅니다

호스트 파일은 typeshade/vite 플러그인으로 .shade.ts를 가져와 그 파일이 내보내는 것을 부릅니다. 헬퍼는 CPU에서 실행되고, @compute 진입점은 지원하는 환경에서 WebGPU 또는 WebGL2로 실행됩니다. 전체 화면 @fragment 진입점은 fs(canvas, bindings)로 캔버스에 그립니다. 바인딩 객체의 타입은 모듈에서 나오므로, 호출이 빠뜨린 바인딩은 호스트 코드의 그 줄에서 타입 오류가 됩니다.

CPU 결과와 대조합니다

같은 모듈을 CPU에서 f64로 실행하고, 테스트가 컴파일러의 산술을 그 결과와 맞춰 봅니다. 출력은 푸시할 때마다 Tint에서 컴파일하고 WebGL2에서 링크합니다. 아래는 같은 파일에서 나온 그라데이션 패스를 두 백엔드가 각각 그린 화면입니다.

TypeScript로 쓰고 TypeShade가 컴파일한 Gradient pass (source twin) 셰이더. 빌드할 때 렌더링함.
생성된 WGSL로 빌드할 때 그린 그라데이션 패스입니다.
TypeScript로 쓰고 TypeShade가 컴파일한 Gradient pass (source twin) 셰이더. 빌드할 때 렌더링함.
생성된 GLSL ES 3.00으로 빌드할 때 그린 그라데이션 패스입니다.

설치

npm에 Git URL을 지정해 저장소를 설치합니다. 설치한 패키지는 typeshade로 import합니다. 설치와 첫 셰이더 예제는 Quick Start에 있습니다. 0.2.0의 레지스트리 배포는 아직 나오지 않았습니다.

Terminal window
npm install https://github.com/typeshade/typeshade.git
npm install --save-dev tsx

Git 의존성은 TypeScript 소스를 포함합니다. Node.js에서 컴파일러와 CLI를 실행할 때는 tsx를 사용합니다.

Playground Quick Start