ネコのために鐘は鳴る

寺院に住み着くパソコ〇好き

WGSL 仕様メモ

WGSL

WGSL (WebGPU Shader Language) の文法や仕様の日本語解説が少ないので自分用メモ。仕様を網羅しているわけではなく、自分が書く上で必要そうな部分を適当に抜粋してまとめた。

Tour of WGSLWGSL の仕様 から日本語に訳して簡単にまとめた。 仕様は長すぎて辛いけど困ったら見るべき。全部書いてある。

定数 (const)

const の型は abstract-int と abstract-float として扱われ、精度は実際の変数に代入する場所で決まる。

変数

let はイミュータブルな変数の宣言。ミュータブル変数は var で宣言する。 varvar<AS> で宣言する。ASアドレス空間 (address spaces)

var<function>

関数内のローカル変数のデフォルトは var<function> であり、普通ローカル変数では単に var で宣言する。 初期値を省略した場合、ゼロ初期化される。

var<private>

シェーダーモジュール内でのグローバル変数。同一モジュール内の全ての関数からアクセス可能。 宣言時の初期値は const-expression でなければならない。省略した場合はゼロ初期化される。 モジュールグローバルでのみ宣言可能。

var<workgroup>

workgroup 内で共有されるミュータブル変数。各呼び出し (invocation) の間では atomicworkgroupBarrier で同期する必要がある。 モジュールグローバルでのみ宣言可能。 初期化式は必要なく、各ワーキンググループの実行時に自動でゼロ初期化される。

var<uniform>

ユニフォーム変数

var<storage>

(自分が使ってないので調べてない)

var<handle>

(自分が使ってないので調べてない)

let

イミュータブルな変数宣言。初期化子が必須。関数内ローカルでのみ宣言可能。 ポインタ型にすることが可能。

属性

@ から始まるのが属性。

@vertex fn vs_main() -> @builtin(position) vec4<f32> {
    ...
}

行列

行列の引数省略時はゼロ初期化。引数は列優先。メモリ上の表現も列優先。

const zero_init = mat3x4<f32>();
/*
| 0 0 0 |
| 0 0 0 |
| 0 0 0 |
| 0 0 0 |
*/

const column_wise = mat3x2<f32>(vec2<f32>(1, 2), vec2<f32>(4, 5), vec2<f32>(6, 7));
/*
| 1 4 6 |
| 2 5 7 |
*/

const scalar_wise = mat2x3<f32>(1, 2, 3, 4, 5, 6);
/*
| 1 4 |
| 2 5 |
| 3 6 |
*/

配列

  • Fixed-Sized Array array<T, N>
  • Runtime-Sized Array array<T>

Fixed-Sized Array の配列長 N は定数式ならリテラルでなくともよい。

const numbers = array<i32, 7>(1, 4, 2, 3);

const three = 3;
const foo: array<i32, three * 2 + 1> = numbers;

Runtime-Sized Array は Storage Buffer のリソースとしてのみ使える。 バッファ全体を表すか、バッファ全体を表す構造体の最後のメンバーとして使う。

素数は実行時に決定され、変数に関連する BufferBinding のサイズに収まる範囲で可能な限り大きくなる。 実行時の配列長は arrayLength で取得できる。

インデックスによって値を取得できるが、配列自体を変数に代入することはできない。

@group(0) @binding(0) var<storage> weights: array<f32>;

struct PointLight { position: vec3f, color: vec3f, }
struct LightData {
  meanIntensity: f32,
  point: array<PointLight>,
}
@group(0) @binding(1) var<storage> lights: LightData;

fn number_of_lights() -> u32 {
    return arrayLength(&lights.point);
}

fn get_point_light(i: i32) -> PointLight {
    return lights.point[i];
}

fn cannot_copy_whole_array() {
    // 通常の変数に代入はできない
    // var all_point_lights = lights.point; // Error
}

構造体 (struct)

struct Data {
  a: f32,
  b: f32,
}

constructible な構造体の場合、() で初期化できる。 constructible とは

  • スカラー型 (u32, f32, etc...)
  • ベクトル型 (vec2<f32>, vec3<f32>, etc...)
  • 行列型 (mat2x2<f32>, mat3x3<f32>, etc...)
  • 要素の型が constructible な固定長配列型 (array<f32, 3>, etc...)
  • メンバーの型が全て constructible な構造体型
const data = Data(1.0, 2.0);

原子性 (atomicity)

uniform, storage, workgroup は並列実行において共有されている。 uniform は読み取り専用なので問題は起こらないが、storageworkgroup は書き込みも可能であるため、競合に注意する必要がある。

atomic<T>i32u32 に対してのみ使える。 atomic<T>workgroupstorageアドレス空間の変数でのみ使える。

var<workgroup> counter: atomic<u32>;

atomic<T> は constructible ではないため、以下の状況では直接使用できない。

  • expression に書けない
  • 関数の引数、戻り値に書けない
  • 変数に代入できない
  • 変数の初期化式に書けない

ビルトイン関数でのみ操作可能で、atomicLoadatomicStore で読み書きできる。 他にも atomicAdd, atomicMin, atomicMax, atomicAnd, atomicOr, atomicXor などがある。

fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)

ポインタ (pointer)

ポインタは ptr<AS, T, AM> で表される。

  • ASアドレス空間 (address spaces)
    • uniform
    • storage
    • workgroup
    • private
    • function
    • handle
  • Tポインターが指す型
  • AM はアクセスモード。省略可能
    • read (default)
    • read_write

アロー演算子 a->b はない。カッコで囲んで (*a).b のように書く。

ユーザー定義関数の引数にポインタを使う時の制約

  • アドレス空間functionprivate でなければならない
  • 変数全体へのポインタのみ使える。複合変数の一部のみのアドレスを渡すことはできない
struct Data {
    aaa: u32,
    bbb: u32,
}
var<private> data: Data;

fn foo() {
  //bar(&data.aaa); // Error: 複合変数の一部のみのアドレスを渡すことはできない
}

式 (expression)

三つの評価フェーズがある。

  • Shader creation time
  • Pipeline creation time
  • Shader execution time

Shader creation time

const-expressions の値を確定します。

Pipeline creation time

override-expressions の値を確定します。

  • override 宣言された値
  • GPUProgrammableStage.constants からの値

Shader execution time

runtime-expressions の値を確定します。

  • letvar 宣言された値
  • 全ての呼び出し関数
  • 変数値
  • 変数への参照やポインタ

リテラル

サフィックスのないリテラルは abstract-int か abstract-float になり、具体的な型付きの変数に束縛される時に型が決まる。

サフィックス
u u32 1u
i i32 1i
f f32 1.0f

@const ビルトイン関数

  • radians(x: T) -> T
  • const_assert(x: bool)
const x = 10;
const_assert(x == 10);  // false ならコンパイルエラー

制御フロー

if

if (condition) { ... }

または

if condition { ... }

switch

switch (condition) { ... }

または

switch condition { ... }

let a = 4;
switch a {
    case 1, 2, 3: {    
    }
    default: {
    }
}

for

for(var i = 0; i < 10; i++) {
}