사전 WGSL
표준

WGSL

gabury1고친 사람 github-actions[bot]

WGSL 은 브라우저가 그래픽 카드에 시킬 계산을 적는 언어입니다. 화면의 점 하나를 무슨 색으로 칠할지를 이 언어로 적어 넘깁니다. 그림과 상관없는 계산도 같은 언어로 적습니다. 남의 기계에서 도는 코드라서, 언어 자체가 남의 메모리를 못 건드리게 막습니다.

쉽고 빠른 이해

WGSL 은 브라우저가 그래픽 카드에 넘길 짧은 프로그램을 적는 언어입니다. 삼각형 한 장을 빨갛게 칠하는 일이라면 「내가 맡은 점의 색은 빨강이다」를 몇 줄로 적어 넘깁니다.

이게 없으면 웹 페이지는 그래픽 카드가 미리 정해 둔 계산만 쓸 수 있습니다. 카드마다 알아듣는 말도 달라서, 한 번 적으면 어느 기계에서나 도는 공통 언어가 필요했습니다.

어떻게 도나:

  1. 사람이 코드를 글자로 적고 브라우저에 문자열로 넘깁니다
  2. 브라우저가 문법과 안전을 검사한 뒤 그 기계의 그래픽 카드가 알아듣는 말로 옮깁니다
  3. 그리기나 계산 명령 한 번에 그 함수가 점 하나마다 · 값 하나마다 수천 번씩 한꺼번에 돕니다

대가는 언어가 좁다는 것입니다. 함수가 자기를 다시 부를 수 없습니다. 도는 도중에 메모리를 더 잡을 수도 없습니다. 검사를 통과해야 돌기 때문에 배열 밖을 읽으려 하면 엉뚱한 메모리 대신 정해진 답이 돌아옵니다.

그래서 브라우저에서 그래픽 카드를 쓰는 코드에 이 언어를 고릅니다. 다른 셰이더 언어로 쌓아 둔 코드가 많으면 옮기는 품을 먼저 셈합니다.

상세

이 절은 WGSL 로 적은 글자가 그래픽 카드에서 도는 프로그램이 되기까지를 따라가고, 그 규칙들이 저마다 무엇을 내주고 얻었는지로 맺습니다.

글자로 넘기고 브라우저가 옮긴다

WGSL(WebGPU Shading Language, WebGPU 셰이딩 언어)은 GPU(Graphics Processing Unit, 그래픽 처리 장치)에서 돌릴 짧은 프로그램을 적는 언어입니다. 그렇게 꽂아 넣는 프로그램을 셰이더라고 부릅니다. 그래픽 카드는 같은 계산을 수천 벌 한꺼번에 돌리라고 만든 장치입니다.

브라우저에서 그래픽 카드를 쓰는 WebGPU 가 이 언어를 자기 셰이더 언어로 정해 두었습니다. 자바스크립트 가 WGSL 소스를 문자열로 넘기면 브라우저가 그것을 받아 컴파일합니다.

미리 빌드해 둔 파일이 아니라 글자를 넘기는 데는 까닭이 있습니다. 그래픽 카드는 회사마다 알아듣는 기계어가 다른데, 웹 페이지는 어느 기계에서 열릴지 모릅니다. 그래서 소스를 글자로 들고 다니다가 페이지가 열린 기계 위에서 그때 옮깁니다.

옮기는 일은 두 걸음입니다. 브라우저가 먼저 소스를 검사해 걸러냅니다. 그다음 그 기계의 그래픽 API(Application Programming Interface, 응용 프로그램 인터페이스)가 쓰는 셰이더 언어로 바꿔 넘깁니다.

윈도우 · 맥 · 리눅스가 저마다 다른 그래픽 API(Direct3D · Metal · Vulkan)를 쓰기 때문에 이 걸음이 필요합니다. 거기서부터는 카드 드라이버가 받아 그 카드의 기계어로 만듭니다.

flowchart TD
    A["WGSL 소스 · 페이지가 들고 있는 문자열"] --> B["브라우저가 검사 · 문법과 안전"]
    B --> C["그 기계의 그래픽 API 가 쓰는 셰이더 언어로 옮김"]
    C --> D["카드 드라이버가 기계어로 컴파일"]
    D --> E["그리기 · 계산 명령 한 번마다 수천 번 실행"]

검사가 맨 앞에 있어서, 문법이 틀렸거나 언어가 막아 둔 것을 쓰면 컴파일 단계에서 오류 메시지로 돌아옵니다. 페이지가 이미 도는 중에 알게 되는 것이므로 소스를 넘기는 쪽은 컴파일 결과를 받아 보는 코드를 함께 씁니다.

타입을 미리 못 박는다

WGSL 은 값마다 타입을 적습니다. 수는 f32 · i32 · u32 셋으로 갈립니다. 소수인지 정수인지 · 부호가 있는지 없는지를 이름이 말합니다.

그래픽 코드에서는 좌표나 색처럼 수 여러 개를 한 덩이로 다루는 일이 잦습니다. 그래서 수 둘을 묶은 vec2f · 수 넷을 묶은 vec4f 같은 타입이 언어에 들어 있습니다. 더하기 한 번으로 네 값을 한꺼번에 더합니다.

이름은 둘로 만듭니다. let 은 한 번 정하면 못 바꾸는 이름입니다. var 는 다른 값을 다시 넣을 수 있는 변수입니다.

타입이 다르면 서로 섞이지 않습니다. 정수와 소수를 같이 계산하려면 바꾸는 함수를 직접 적어야 합니다. 도는 도중에 소리 없이 바꿔 주는 암묵적 형변환이 없어서, 어긋난 계산은 컴파일에서 걸립니다.

wgsl
let n: i32 = 3;
let x: f32 = f32(n) * 0.5; // 1.5

함수 앞의 꼬리표가 단계를 정한다

WGSL 파일 하나에는 함수가 여럿 들어갑니다. 그중 그래픽 카드가 직접 부를 함수 앞에는 어느 단계의 셰이더인지를 붙입니다. 이렇게 함수나 값 앞에 붙여 성격을 밝히는 꼬리표를 애트리뷰트라고 부르고 @ 로 시작합니다.

그리기에 쓰는 단계는 둘입니다. 도형의 꼭짓점을 화면 어디에 놓을지 정하는 정점 셰이더와, 화면의 점 하나를 무슨 색으로 칠할지 정하는 프래그먼트 셰이더입니다.

wgsl
@vertex
fn vs(@location(0) pos: vec2f)
    -> @builtin(position) vec4f {
  return vec4f(pos, 0, 1); // 수 둘 → 수 넷
}

@fragment
fn fs() -> @location(0) vec4f {
  return vec4f(1, 0, 0, 1); // 빨강 한 색
}

@location(0) 은 바깥에서 넘어온 값이 몇 번 통로로 들어오는지를 밝힌 것입니다. 나가는 값에 붙으면 몇 번 통로로 내보내는지를 밝힌 것입니다. 그래서 같은 꼬리표가 정점 함수의 인자에도, 프래그먼트 함수의 반환에도 붙습니다.

@builtin(position) 은 그래픽 카드가 이름으로 알아보는 출력에 값을 내놓는다는 뜻입니다. 여기에 넣은 좌표를 카드가 받아 다음 단계로 넘깁니다.

flowchart TD
    A["정점 데이터 · 꼭짓점 좌표"] -->|"@location(0)"| B["정점 셰이더 vs"]
    B -->|"@builtin(position)"| C["그래픽 카드"]
    C --> D["프래그먼트 셰이더 fs"]
    D -->|"@location(0)"| E["화면의 점 색"]

그림과 상관없는 계산을 시킬 때는 컴퓨트 셰이더를 씁니다. 이때는 함수 앞에 몇 개를 한 묶음으로 돌릴지도 같이 붙입니다.

wgsl
@compute @workgroup_size(64)
fn add(@builtin(global_invocation_id) i: vec3u) {
  c[i.x] = a[i.x] + b[i.x];  // 한 칸 더하기
}

@workgroup_size(64) 는 이 함수를 예순네 개씩 묶어 한꺼번에 돌린다는 뜻입니다. 그 묶음 안에서 도는 하나하나를 호출이라고 부릅니다.

global_invocation_id 는 지금 도는 호출이 전체에서 몇 번째인지를 알려 주는 번호입니다. 함수는 그 번호를 보고 자기가 맡을 칸을 고릅니다. 번호는 묶음을 가로질러 이어집니다. 첫 묶음이 0번부터 63번까지를 맡고 다음 묶음은 64번부터입니다.

flowchart TD
    A["계산 명령 한 번"]
    subgraph W0["워크그룹 0"]
        I0["호출 · id 0"] ---|"가운데 예순두 개 접음"| I63["호출 · id 63"]
    end
    subgraph W1["워크그룹 1"]
        I64["호출 · id 64"] ---|"가운데 예순두 개 접음"| I127["호출 · id 127"]
    end
    R["나머지 묶음"]
    A --> W0
    A --> W1
    A --> R

값을 어디서 받아 오나

셰이더가 다루는 데이터는 함수 인자로만 들어오지 않습니다. 이미지나 큰 배열처럼 덩치 큰 값은 그래픽 카드 쪽 메모리에 미리 올려 둡니다. 셰이더는 그것을 가리키는 이름만 가집니다.

그 이름이 어느 덩어리를 가리키는지는 번호 두 개로 밝힙니다. 그 번호 두 개를 @group 과 @binding 이라고 씁니다. 앞의 @group 은 묶음을 가리킵니다. 뒤의 @binding 은 그 묶음 안에서 몇 번째인지를 가리킵니다.

바깥에서 그리기 명령을 낼 때 같은 번호에 실제 데이터를 꽂아 줍니다. 셰이더는 번호만 보고 그것을 집습니다.

이름 앞에는 그 데이터가 그래픽 카드 메모리의 어느 갈래에 놓이는지도 붙입니다. 이 갈래를 주소 공간이라고 부릅니다. 모두가 같은 값을 읽기만 하는 데이터는 uniform 입니다. 큰 배열을 읽고 쓰는 데이터는 storage 입니다.

앞 소절에서 예순네 개씩 묶은 그 묶음의 이름이 워크그룹입니다. 그 묶음 안에서만 나눠 쓰는 메모리 갈래에도 같은 이름 workgroup 을 씁니다. 실행의 묶음과 메모리 갈래가 이름을 함께 쓰는 것입니다.

flowchart TD
    JS1["자바스크립트가 꽂는 데이터 · 색 한 값"]
    JS2["자바스크립트가 꽂는 데이터 · 수 배열"]
    subgraph G0["묶음 0 · @group(0)"]
        B0["@binding(0) · uniform · color"]
        B1["@binding(1) · storage · data"]
    end
    JS1 --> B0
    JS2 --> B1

소스에서는 이름 앞에 그 번호와 갈래를 함께 붙입니다.

wgsl
@group(0) @binding(0)
var<uniform> color: vec4f;

@group(0) @binding(1)
var<storage, read_write> data: array<f32>;

@group(0) @binding(1) 이 위에서 말한 번호 두 개입니다. read_write 는 이 데이터를 읽기와 쓰기 둘 다 한다는 뜻입니다.

갈래를 나누는 까닭은 카드 안에서 메모리의 성격이 갈리기 때문입니다. 모두가 읽기만 하는 값은 카드가 미리 준비해 둔 경로로 넘길 수 있습니다. 여럿이 고쳐 쓰는 값은 그렇게 못 합니다. 어느 쪽인지를 소스에 못 박아 두어야 카드가 그 결정을 컴파일할 때 내릴 수 있습니다.

남의 코드를 돌리려고 언어가 막는 것

웹 페이지의 셰이더는 남이 보낸 코드입니다. 그것을 내 그래픽 카드에서 돌리는 것이므로, 언어가 미리 막지 않으면 다른 페이지가 올려 둔 그림이나 메모리를 읽어 갈 수 있습니다.

그래서 배열 범위를 벗어난 읽기와 쓰기에 정해진 답을 둡니다. 범위 밖을 가리키면 그 접근을 배열 안으로 끌어당기거나 없던 일로 만듭니다. 무엇이 나올지 모르는 미정의 동작을 남기지 않는 것이 이 언어의 규칙입니다.

대가는 언어가 좁아진다는 것입니다. 함수가 자기를 다시 부르는 재귀가 없습니다. 도는 도중에 메모리를 더 잡는 일도 없습니다. 쓸 메모리의 크기가 프로그램을 올릴 때 정해집니다.

규칙이 하나 더 있습니다. 그래픽 카드는 같은 함수를 수천 벌 한꺼번에 돌립니다. 이미지에서 색을 읽는 함수는 옆에서 같이 도는 호출들의 값을 함께 봐야 합니다. 그래서 if 로 갈라져 일부만 들어가는 대목에서는 부를 수 없습니다.

같이 도는 호출이 모두 같은 길로 가야 한다는 이 규칙을 균일 제어 흐름(uniform control flow)이라고 부릅니다. 브라우저가 컴파일할 때 이것을 따져 보고 어긋나면 오류로 돌려보냅니다.

고른 것과 내준 것

위 규칙은 저마다 무언가를 내주고 얻은 것입니다.

고른 것 얻는 것 내준 것
소스를 글자로 넘긴다 어느 기계에서 열릴지 몰라도 그 기계에 맞춰 컴파일한다 문법 오류를 페이지가 도는 중에 안다
범위 밖 접근에 정해진 답을 둔다 남이 보낸 셰이더를 돌려도 다른 페이지의 메모리가 안 새 나간다 접근마다 확인이 붙고, 실수한 코드가 잘린 값을 조용히 받는다
재귀와 메모리 더 잡기를 뺀다 쓸 메모리를 미리 셈해 두고 돌린다 재귀로 짜던 알고리즘을 반복문으로 다시 짠다
값마다 타입을 적게 한다 어긋난 계산이 컴파일에서 걸린다 짧은 코드에도 타입을 쓰는 손이 든다
웹이 받는 셰이더 언어를 하나로 정한다 플랫폼마다 셰이더를 따로 짜지 않는다 다른 언어로 쌓아 둔 셰이더는 옮겨 적는 품이 든다

그래서 WGSL 은 브라우저에서 그래픽 카드를 쓰는 코드에 먼저 고릅니다. 반대로 GLSL(OpenGL Shading Language, OpenGL 셰이딩 언어)처럼 다른 셰이더 언어로 쌓아 둔 코드가 많으면 옮기는 품이 듭니다. 언어의 규칙은 남이 보낸 코드를 돌려야 하는 브라우저 쪽 사정에서 나왔습니다.

관련 항목

WGSL 로 적는 셰이더 단계

셰이더 · 정점 셰이더 · 프래그먼트 셰이더 · 컴퓨트 셰이더 · 셰이더 단계 · 셰이더 프로그램 · 셰이더 모듈

WGSL 소스를 넘겨 실행하는 웹 쪽 구성 요소

WebGPU · 브라우저 · JavaScript · GPU · 캔버스

같은 몫을 맡는 다른 셰이더 언어

GLSL · HLSL · MSL · SPIR-V · Open Shading Language

WGSL 소스가 기계어가 되기까지 거치는 단계

셰이더 컴파일 · 셰이더 컴파일러 · 트랜스파일 · 중간 표현 · 디바이스 드라이버 · 셰이더 캐시 · 컴파일러

WGSL 소스가 옮겨져 도는 플랫폼별 그래픽 API

Vulkan · Direct3D · Metal · OpenGL

WGSL 앞에 웹에서 그림을 그리던 그래픽 API 와 그 언어

WebGL · GLSL ES · OpenGL ES

WGSL 이 그래픽 카드 메모리를 가리키는 방식

주소 공간 · 유니폼 버퍼 · 스토리지 버퍼 · 바인드 그룹 · 텍스처 · 샘플러 · 워크그룹

WGSL 코드가 다루는 값의 꼴

벡터 · 행렬 · 부동소수점 · 구조체 · 배열 · 정적 타입 · 암묵적 형변환

WGSL 이 남의 코드를 돌리려고 두는 제약과 안전 성질

메모리 안전 · 샌드박스 · 경계 검사 · 미정의 동작 · 데이터 경쟁 · 재귀 · 균일 제어 흐름

WGSL 이 그린 값이 화면이 되기까지 거치는 처리 단계

그래픽스 파이프라인 · 렌더링 · 래스터화 · 프레임 버퍼 · 정점 속성

WGSL 이 속하는 상위 분류

프로그래밍 언어 · 셰이딩 언어 · 도메인 특화 언어 · 병렬 처리 · SIMD

다른 이름: WebGPU Shading Language · WebGPU 셰이딩 언어 · 더블유지에스엘