目次

webgpufundamentals.org

Fix, Fork, Contribute

WebGPU WGSL

この記事はGemini Code Assistによって自動翻訳されました。翻訳に問題がある場合は、お手数ですがこちらからPull Requestを送信してください。

WGSLの詳細な概要については、WGSLツアーを参照してください。また、実際のWGSL仕様もありますが、言語弁護士向けに書かれているため、処理が難しい場合があります😂

この記事は、すでにプログラミングの方法を知っていることを前提としているため、WGSLの例を見るだけで、何が表示されているかを理解できる可能性が高いです。おそらく簡潔すぎますが、WGSLシェーダープログラムの理解と作成に役立つことを願っています。

WGSLは厳密に型付けされています

JavaScriptとは異なり、WGSLでは、すべての変数、構造体フィールド、関数パラメータ、および関数戻り値の型を知る必要があります。typescript、rust、C++、C#、Java、Swift、Kotlinなどを使用したことがある場合は、これに慣れています。

プレーン型

WGSLのプレーン型は次のとおりです。

  • i32 32ビット符号付き整数
  • u32 32ビット符号なし整数
  • f32 32ビット浮動小数点数
  • bool ブール値
  • f16 16ビット浮動小数点数(これは、確認して要求する必要があるオプション機能です)

変数宣言

JavaScriptでは、次のように変数と関数を宣言できます。

var a = 1;
let c = 3;
function d(e) { return e * 2; }

WGSLでは、それらの完全な形式は次のようになります。

var a: f32 = 1;
let c: f32 = 3;
fn d(e: f32) -> f32 { return e * 2; }

上記から注意すべき重要な点は、変数宣言に: f32のような: <type>を追加し、関数宣言に-> <type>を追加する必要があることです。

自動型

WGSLには、変数のショートカットがあります。typescriptと同様に、変数の型を宣言しない場合、右側の式の型に自動的になります。

fn foo() -> bool { return false; }

var a = 1;     // aはi32です
let b = 2.0;   // bはf32です
var c = 3u;    // cはu32です
var d = foo(); // dはboolです

型変換

さらに、厳密に型付けされているということは、多くの場合、型を変換する必要があることを意味します。

let a = 1;     // aはi32です
let b = 2.0;   // bはf32です
*let c = a + b; // エラー:i32をf32に追加できません

修正は、一方を他方に変換することです。

let a = 1;     // aはi32です
let b = 2.0;   // bはf32です
let c = f32(a) + b; // ok

しかし、WGSLには「AbstractInt」と「AbstractFloat」と呼ばれるものがあります。これらは、まだ型が決定されていない数値と考えることができます。これらはコンパイル時のみの機能です。

let a = 1;            // aはi32です
let b = 2.0;          // bはf32です
*let c = a + b;       // エラー:i32をf32に追加できません
let d = 1 + 2.0;      // dはf32です

数値サフィックス

2i   // i32
3u   // u32
4f   // f32
4.5f // f32
5h   // f16
5.6h // f16
6    // AbstractInt
7.0  // AbstractFloat

letvarconstは、WGSLとJavaScriptで意味が異なります。

JavaScriptでは、varは関数スコープを持つ変数です。letはブロックスコープを持つ変数です。constは、ブロックスコープを持つ定数変数(変更不可)です[1]

WGSLでは、すべての変数はブロックスコープを持ちます。varはストレージを持つ変数であり、したがって変更可能です。letは定数値です。

fn foo() {
  let a = 1;
*  a = a + 1;  // エラー:aは定数式です
  var b = 2;
  b = b + 1;  // ok
}

constは変数ではなく、コンパイル時の定数です。実行時に発生するものにconstを使用することはできません。

const one = 1;              // ok
const two = one * 2;        // ok
const PI = radians(180.0);  // ok

fn add(a: f32, b: f32) -> f32 {
*  const result = a + b;   // エラー:constはコンパイル時式でのみ使用できます
  return result;
}

ベクトル型

WGSLには、vec2vec3vec4の3つのベクトル型があります。基本的なスタイルはvec?<type>なので、vec2<i32>(2つのi32のベクトル)、vec3<f32>(3つのf32のベクトル)、vec4<u32>(4つのu32のベクトル)、vec3<bool>(3つのブール値のベクトル)です。

例:

let a = vec2<i32>(1, -2);
let b = vec3<f32>(3.4, 5.6, 7.8);
let c = vec4<u32>(9, 10, 11, 12);

アクセサー

さまざまなアクセサーを使用して、ベクトル内の値にアクセスできます。

let a = vec4<f32>(1, 2, 3, 4);
let b = a.z;   // x、y、z、w経由
let c = a.b;   // r、g、b、a経由
let d = a[2];  // 配列要素アクセサー経由

上記では、bcdはすべて同じです。それらはすべてaの3番目の要素にアクセスしています。それらはすべて「3」です。

スウィズル

複数の要素にアクセスすることもできます。

let a = vec4<f32>(1, 2, 3, 4);
let b = a.zx;   // x、y、z、w経由
let c = a.br;   // r、g、b、a経由
let d = vec2<f32>(a[2], a[0]);

上記では、bcdはすべて同じです。それらはすべてvec2<f32>(3, 1)です。

要素を繰り返すこともできます。

let a = vec4<f32>(1, 2, 3, 4);
let b = vec3<f32>(a.z, a.z, a.y);
let c = a.zzy;

上記では、bcは同じです。どちらも、内容が3、3、2のvec3<f32>です。

ベクトルのショートカット

基本型にはショートカットがあります。<i32>iに、<f32>fに、<u32>uに、<f16>hに変更します。

let a = vec4<f32>(1, 2, 3, 4);
let b = vec4f(1, 2, 3, 4);

abは同じ型です。

ベクトル構築

ベクトルは、より小さな型で構築できます。

let a = vec4f(1, 2, 3, 4);
let b = vec2f(2, 3);
let c = vec4f(1, b, 4);
let d = vec4f(1, a.yz, 4);
let e = vec4f(a.xyz, 4);
let f = vec4f(1, a.yzw);

acdefは同じです。

ベクトル演算

ベクトルで数学を行うことができます。

let a = vec4f(1, 2, 3, 4);
let b = vec4f(5, 6, 7, 8);
let c = a + b;  // cはvec4f(6, 8, 10, 12)です
let d = a * b;  // dはvec4f(5, 12, 21, 32)です
let e = a - b;  // eはvec4f(-4, -4, -4, -4)です

多くの関数はベクトルでも機能します。

let a = vec4f(1, 2, 3, 4);
let b = vec4f(5, 6, 7, 8);
let c = mix(a, b, 0.5);                   // cはvec4f(3, 4, 5, 6)です
let d = mix(a, b, vec4f(0, 0.5, 0.5, 1)); // dはvec4f(1, 4, 5, 8)です

行列

WGSLには多数の行列型があります。行列はベクトルの配列です。形式はmat<numVectors>x<vectorSize><<type>>なので、たとえばmat3x4<f32>は3つのvec4<f32>の配列です。ベクトルと同様に、行列にも同じショートカットがあります。

let a: mat4x4<f32> = ...
let b: mat4x4f = ...

abは同じ型です。

行列ベクトルアクセス

配列構文を使用して、行列のベクトルを参照できます。

let a = mat4x4f(...);
let b = a[2];   // bはaの3番目のベクトルのvec4fです

3D計算で最も一般的な行列型はmat4x4fであり、vec4fと直接乗算して別のvec4fを生成できます。

let a = mat4x4f(....);
let b = vec4f(1, 2, 3, 4);
let c = a * b;  // cはvec4fであり、a * bの結果です

配列

WGSLの配列は、array<type, numElements>構文で宣言されます。

let a = array<f32, 5>;   // 5つのf32の配列
let b = array<vec4f, 6>; // 6つのvec4fの配列

しかし、arrayコンストラクタもあります。任意の数の引数を受け取り、配列を返します。引数はすべて同じ型でなければなりません。

let arrOf3Vec3fsA = array(vec3f(1,2,3), vec3f(4,5,6), vec3f(7,8,9));
let arrOf3Vec3fsB = array<vec3f, 3>(vec3f(1,2,3), vec3f(4,5,6), vec3f(7,8,9));

上記では、arrOf3Vec3fsAarrOf3Vec3fsBと同じです。

残念ながら、WGSLのバージョン1の時点では、固定サイズの配列のサイズを取得する方法はありません。

ランタイムサイズの配列

ルートスコープのストレージ宣言にある配列、またはルートスコープ構造体の最後のフィールドとしてある配列は、サイズなしで指定できる唯一の配列です。

struct Stuff {
  color: vec4f,
  size: f32,
  verts: array<vec3f>,
};
@group(0) @binding(0) var<storage> foo: array<mat4x4f>;
@group(0) @binding(1) var<storage> bar: Stuff;

foobar.vertsの要素数は、実行時に使用されるバインドグループの設定によって定義されます。WGSLでarrayLengthを使用してこのサイズを照会できます。

@group(0) @binding(0) var<storage> foo: array<mat4x4f>;
@group(0) @binding(1) var<storage> bar: Stuff;

...
  let numMatrices = arrayLength(&foo);
  let numVerts = arrayLength(&bar.verts);

関数

WGSLの関数は、fn name(parameters) -> returnType { ..body... }のパターンに従います。

fn add(a: f32, b: f32) -> f32 {
  return a + b;
}

エントリポイント

WGSLプログラムにはエントリポイントが必要です。エントリポイントは、@vertex@fragment、または@computeのいずれかで指定されます。

@vertex fn myFunc(a: f32, b: f32) -> @builtin(position): vec4f {
  return vec4f(0, 0, 0, 0);
}

シェーダーは、エントリポイントがアクセスするもののみを使用します。

@group(0) @binding(0) var<uniforms> uni: vec4f;

vec4f fn foo() {
  return uni;
}

@vertex fn vs1(): @builtin(position) vec4f {
  return vec4f(0);
}

@vertex fn vs2(): @builtin(position) vec4f {
  return foo();
}

上記では、univs1によってアクセスされないため、パイプラインでvs1を使用する場合、必須のバインディングとして表示されません。vs2は、fooを呼び出すことによって間接的にuniを参照するため、パイプラインでvs2を使用する場合、必須のバインディングとして表示されます。

属性

属性という言葉は、WebGPUで2つの意味を持ちます。1つは、頂点バッファに関する記事で説明されている頂点属性です。もう1つは、@で始まるWGSLの属性です。

@location(number)

@location(number)は、シェーダーの入力と出力を定義するために使用されます。

頂点シェーダーの入力

頂点シェーダーの場合、入力は頂点シェーダーのエントリポイント関数の@location属性によって定義されます。

@vertex vs1(@location(0) foo: f32, @location(1) bar: vec4f) ...

struct Stuff {
  @location(0) foo: f32,
  @location(1) bar: vec4f,
};
@vertex vs2(s: Stuff) ...

vs1vs2の両方が、頂点バッファによって供給される必要がある場所0と1の頂点シェーダーへの入力を宣言します。

ステージ間変数

ステージ間変数の場合、@location属性は、変数がシェーダー間で渡される場所を定義します。

struct VSOut {
  @builtin(position) pos: vec4f,
  @location(0) color: vec4f,
  @location(1) texcoords: vec2f,
};

struct FSIn {
  @location(1) uv: vec2f,
  @location(0) diffuse: vec4f,
};

@vertex fn foo(...) -> VSOut { ... }
@fragment fn bar(moo: FSIn) ... 

上記では、頂点シェーダーfoolocation(0)colorvec4fとして、location(1)texcoordsvec2fとして渡します。フラグメントシェーダーbarは、場所が一致するため、それらをuvdiffuseとして受け取ります。

フラグメントシェーダーの出力

フラグメントシェーダーの場合、@locationは、結果を格納するGPURenderPassDescriptor.colorAttachmentを指定します。

struct FSOut {
  @location(0) albedo: vec4f;
  @location(1) normal: vec4f;
}
@fragment fn bar(...) -> FSOut { ... }

@builtin(name)

@builtin属性は、特定の変数の値がWebGPUの組み込み機能から取得されることを指定するために使用されます。

@vertex fn vs1(@builtin(vertex_index) foo: u32, @builtin(instance_index) bar: u32) ... {
  ...
}

上記では、fooは組み込みのvertex_indexから値を取得し、barは組み込みのinstance_indexから値を取得します。

struct Foo {
  @builtin(vertex_index) vNdx: u32,
  @builtin(instance_index) iNdx: u32,
}
@vertex fn vs1(blap: Foo) ... {
  ...
}

上記では、blap.vNdxは組み込みのvertex_indexから値を取得し、blap.iNdxは組み込みのinstance_indexから値を取得します。

組み込み名 ステージ IO 説明
vertex_index 頂点 入力 u32 現在のAPIレベルの描画コマンド内の現在の頂点のインデックス。描画インスタンス化とは無関係です。

非インデックス描画の場合、最初の頂点のインデックスは、直接または間接的に提供される描画のfirstVertex引数と等しくなります。 インデックスは、描画インスタンス内の追加の頂点ごとに1ずつインクリメントされます。

インデックス描画の場合、インデックスは、頂点のインデックスバッファエントリに、直接または間接的に提供される描画のbaseVertex引数を加えたものと等しくなります。

instance_index 頂点 入力 u32 現在のAPIレベルの描画コマンド内の現在の頂点のインスタンスインデックス。

最初のインスタンスのインデックスは、直接または間接的に提供される描画のfirstInstance引数と等しくなります。 インデックスは、描画内の追加のインスタンスごとに1ずつインクリメントされます。

position 頂点 出力 vec4<f32> 同次座標を使用した現在の頂点の出力位置。 同次正規化(xyzの各成分がw成分で除算される)の後、位置はWebGPU正規化デバイス座標空間にあります。 WebGPU § 3.3 座標系を参照してください。
フラグメント 入力 vec4<f32> フレームバッファ空間内の現在のフラグメントのフレームバッファ位置。 (xyzの成分は、wが1になるようにすでにスケーリングされています。) WebGPU § 3.3 座標系を参照してください。
front_facing フラグメント 入力 bool 現在のフラグメントが前面プリミティブ上にある場合はtrue。 それ以外の場合はfalse。
frag_depth フラグメント 出力 f32 ビューポート深度範囲内のフラグメントの更新された深度。 WebGPU § 3.3 座標系を参照してください。
local_invocation_id コンピュート 入力 vec3<u32> 現在の呼び出しのローカル呼び出しID、 つまり、ワークグループグリッド内の位置。
local_invocation_index コンピュート 入力 u32 現在の呼び出しのローカル呼び出しインデックスワークグループグリッド内の呼び出しの位置の線形化されたインデックス。
global_invocation_id コンピュート 入力 vec3<u32> 現在の呼び出しのグローバル呼び出しID、 つまり、コンピュートシェーダーグリッド内の位置。
workgroup_id コンピュート 入力 vec3<u32> 現在の呼び出しのワークグループID、 つまり、コンピュートシェーダーグリッド内のワークグループの位置。
num_workgroups コンピュート 入力 vec3<u32> ディスパッチサイズvec<u32>(group_count_x, group_count_y, group_count_z)、APIによってディスパッチされたコンピュートシェーダーの。
sample_index フラグメント 入力 u32 現在のフラグメントのサンプルインデックス。 値は少なくとも0で、最大でsampleCount-1です。ここで、sampleCountは、GPUレンダーパイプラインに指定されたMSAAサンプルcountです。
WebGPU § 10.3 GPURenderPipelineを参照してください。
sample_mask フラグメント 入力 u32 現在のフラグメントのサンプルカバレッジマスク。 このフラグメント内のどのサンプルがレンダリングされるプリミティブでカバーされているかを示すビットマスクが含まれています。
WebGPU § 23.3.11 サンプルマスキングを参照してください。
フラグメント 出力 u32 現在のフラグメントのサンプルカバレッジマスク制御。 この変数に書き込まれた最後の値がシェーダー出力マスクになります。 書き込まれた値のゼロビットにより、カラーアタッチメントの対応するサンプルが破棄されます。
WebGPU § 23.3.11 サンプルマスキングを参照してください。

フロー制御

ほとんどのコンピュータ言語と同様に、WGSLにはフロー制御ステートメントがあります。

for

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

if

    if (i < 5) {
      ...
    } else if (i > 7) {
      ..
    } else {
      ...
    }

while

  var j = 0;
  while (j < 5) {
    ...
    j++;
  }

loop

  var k = 0;
  loop {
    k++;
    if (k >= 5) {
      break;
    }
  }

break

  var k = 0;
  loop {
    k++;
    if (k >= 5) {
      break;
    }
  }

break if

  var k = 0;
  loop {
    k++;
    break if (k >= 5);
  }

continue

  for (var i = 0; i < 10; ++i) {
    if (i % 2 == 1) {
      continue;
    }
    ...
  }

continuing

  for (var i = 0; i < 10; ++i) {
    if (i % 2 == 1) {
      continue;
    }
    ...

    continuing {
      // continueはここに行きます
      ...
    }
  }

discard

   if (v < 0.5) {
     discard;
   }

discardはシェーダーを終了します。フラグメントシェーダーでのみ使用できます。

switch

var a : i32;
let x : i32 = generateValue();
switch x {
  case 0: {      // コロンはオプションです
    a = 1;
  }
  default {      // デフォルトは最後に表示する必要はありません
    a = 2;
  }
  case 1, 2, {   // 複数のセレクター値を使用できます
    a = 3;
  }
  case 3, {      // 末尾のカンマはオプションです
    a = 4;
  }
  case 4 {
    a = 5;
  }
}

switchu32またはi32でのみ機能し、ケースは定数でなければなりません。

演算子

名前 演算子 結合性 バインディング
括弧付き (...)
プライマリ a(), a[], a.b 左から右
単項 -a, !a, ~a, *a, &a 右から左 上記すべて
乗法 a * b, a / b, a % b 左から右 上記すべて
加法 a + b, a - b 左から右 上記すべて
シフト a << b, a >> b 括弧が必要 単項
関係 a < b, a > b, a <= b, a >= b, a == b, a != b 括弧が必要 上記すべて
バイナリAND a & b 左から右 単項
バイナリXOR a ^ b 左から右 単項
バイナリOR a | b 左から右 単項
短絡AND a && b 左から右 関係
短絡OR a || b 左から右 関係

組み込み関数

WGSL関数リファレンスを参照してください。

他の言語との違い

ifwhileswitchbreak-if式には括弧は必要ありません。

if a < 5 {
  doTheThing();
}

三項演算子なし

多くの言語には、三項演算子condition ? trueExpression : falseExpressionがあります。WGSLにはありません。WGSLにはselectがあります。

  let a = select(falseExpression, trueExpression, condition);

++--は式ではなく、ステートメントです。

多くの言語には、前置インクリメント後置インクリメント演算子があります。

// JavaScript
let a = 5;
let b = a++;  // b = 5, a = 6  (後置インクリメント)
let c = ++a;  // c = 7, a = 7  (前置インクリメント)

WGSLにはどちらもありません。インクリメントおよびデクリメントステートメントのみがあります。

// WGSL
var a = 5;
a++;          // は現在6です
*++a;          // エラー:前置インクリメントのようなものはありません
*let b = a++;  // エラー:a++は式ではなく、ステートメントです

+=-=は式ではなく、代入ステートメントです。

// JavaScript
let a = 5;
a += 2;          // a = 7
let b = a += 2;  // a = 9, b = 9
// WGSL
let a = 5;
a += 2;           // aは7です
*let b = a += 2;  // エラー:a += 2は式ではありません

スウィズルは左側に表示できません。

一部の言語ではそうですが、WGSLではそうではありません。

var color = vec4f(0.25, 0.5, 0.75, 1);
*color.rgb = color.bgr; // エラー
color = vec4(color.bgr, color.a);  // OK

注:この機能を追加する提案があります。

_への偽の代入

_は、何かを使用済みのように見せるが、実際には使用しないように代入できる特別な変数です。

@group(0) @binding(0) var<uniforms> uni1: vec4f;
@group(0) @binding(0) var<uniforms> uni2: mat4x4f;

@vertex fn vs1(): @builtin(position) vec4f {
  return vec4f(0);
}

@vertex fn vs2(): @builtin(position) vec4f {
  _ = uni1;
  _ = uni2;
  return vec4f(0);
}

上記では、uni1uni2vs1によってアクセスされないため、パイプラインでvs1を使用する場合、必須のバインディングとして表示されません。vs2uni1uni2の両方を参照するため、パイプラインでvs2を使用する場合、両方とも必須のバインディングとして表示されます。


  1. JavaScriptの変数は、undefinednullbooleannumberstringreference-to-objectの基本型を保持します。プログラミングに慣れていない人にとって、const o = {name: 'foo'}; o.name = 'bar';が機能するのは、oconstとして宣言されているため、混乱する可能性があります。重要なのは、oが定数であるということです。これは、オブジェクトへの定数参照です。oが参照するオブジェクトを変更することはできません。オブジェクト自体を変更することはできます。 ↩︎

問題点/バグ? githubでissueを作成.
comments powered by Disqus