WebGPUの計算シェーダ
描画を経由せずGPUの並列計算をブラウザから直接引き出せる。WGSLとバインドグループでWebGLにはない汎用GPU計算の作法が身につきます。
- 1.コンピュートシェーダは描画パイプラインを介さず、ワークグループ単位でGPUコアに計算を並列分配する専用の実行経路。
- 2.WGSLはCPU/GPU間の型を静的に固定する言語で、バッファはバインドグループを介して明示的にシェーダへ結びつける。
- 3.WebGLは描画への偽装でしかGPU計算を扱えなかったが、WebGPUはパイプライン構築を前倒し検証し、推論やシミュレーションを正式にサポートする。
描画を経由しない、もう一つのGPU実行経路
WebGPUには描画用のGPURenderPipelineとは別に、計算専用のGPUComputePipelineがあります。頂点シェーダ・ラスタライザ・フラグメントシェーダという描画段階を一切通らず、GPUの並列演算器へ直接ジョブを投げる経路です。Canvas 2D/WebGL/WebGPUの描画モデルの違いで見た「パイプラインに状態を固め、コマンドバッファで一括投入する」という設計はここでも同じですが、コンピュートシェーダは画面のピクセルを塗るためではなく、任意のデータ変換のために存在します。行列演算・物理シミュレーション・画像フィルタ・機械学習の推論など、出力が「画面」ではなく「バッファの中身」である処理はすべてこの経路に載ります。
WebGLにもGPGPU(汎用GPU計算)を行うテクニックはありましたが、それは本質的に「計算結果をテクスチャに描画したふりをして取り出す」という迂回策でした。フラグメントシェーダは1ピクセルにつき1回しか呼ばれず、出力先は必ずフレームバッファかテクスチャという制約に縛られていたためです。WebGPUのコンピュートシェーダはこの制約を取り払い、任意サイズのバッファへ任意の順序で書き込める点が構造的な違いです。
ワークグループという並列実行の単位
コンピュートシェーダの実行単位はワークグループです。dispatchWorkgroups(x, y, z)を呼ぶと、GPUは指定された3次元グリッド分のワークグループを生成し、各ワークグループの内部にはシェーダ側で宣言したローカルなスレッド集合(workgroup_size)が並びます。全体のスレッド数は「dispatchの引数×workgroup_sizeの積」で決まり、GPUはこれをコア間に自動分配して並列実行します。
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let i = gid.x;
output[i] = input[i] * 2.0;
}
ここで重要なのは、global_invocation_idがスレッドごとに異なる値を持つ組み込み変数であり、各スレッドはそれを使って「自分が担当する配列の添字」を自力で計算する点です。CPUのforループのように順番に回るのではなく、数千のスレッドが同時に、それぞれ自分のインデックスだけを見て動きます。ワークグループサイズ(この例では64)はGPUのSIMD幅やレジスタ制約に合わせてチューニング対象になり、小さすぎるとGPUの並列度を使い切れず、大きすぎるとレジスタ不足でスケジューリング効率が落ちます。
同一ワークグループ内のスレッドはvar<workgroup>で宣言した高速な共有メモリを介して協調できます。リダクション(総和・最大値探索)やタイル化した行列積では、まずワークグループ内でこの共有メモリに部分結果を書き込み、workgroupBarrier()で全スレッドの書き込み完了を待ってから集約する、という手順が定石です。
WGSL:型を静的に固定するシェーダ言語
WGSL(WebGPU Shading Language)はWebGPU専用に設計された言語で、GLSLと違い型を厳格に静的検査します。f32・u32・vec3<f32>のように型パラメータを明示し、暗黙の型変換にも寛容ではありません。これは事故を防ぐためだけでなく、WebGPUの実装がWGSLのソースをコンパイル時に検証し、背後のネイティブAPI(Vulkan/Metal/Direct3D 12)向けの中間表現へ安全に変換するための前提でもあります。パイプライン生成(createComputePipeline)の時点でこの検証が走るため、WebGLでありがちだった「実行時にシェーダのリンクが失敗する」不確実性が構築時に前倒しされます。
バッファ側のデータ型とWGSL側の型は一致させる必要があり、ずれるとバイト境界がずれて壊れた値を読むことになります。特にvec3<f32>はGPU側で16バイト境界にアラインされる(実質vec4相当の領域を占める)など、CPU側のTypedArrayでのメモリレイアウトとGPU側の構造体レイアウトを手動で合わせ込む作業が発生します。
バッファとバインドグループ:リソースを明示的に結ぶ
WebGPUではシェーダが参照するリソース(バッファ・テクスチャ・サンプラ)を、コードの外側にある文字列名ではなく、バインドグループという番号付きの束で結びます。手順は次の3段階です。
// 1. GPUバッファを確保(storageフラグで読み書き可能に)
const inputBuf = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(inputBuf, 0, data);
// 2. バインドグループレイアウトに沿ってバッファを束ねる
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: inputBuf } },
{ binding: 1, resource: { buffer: outputBuf } },
],
});
// 3. コンピュートパスに差してディスパッチ
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(data.length / 64));
pass.end();
device.queue.submit([encoder.finish()]);
バインドグループはパイプラインのレイアウトと厳密に一致している必要があり、@group(0) @binding(0)というWGSL側の注釈が、JS側のbinding: 0と1対1で対応します。この明示性こそがWebGLとの最大の違いです。WebGLはgl.bindBufferでグローバルな「現在の状態」を書き換えて描画に影響させましたが、WebGPUのバインドグループはパイプライン専用の閉じた対応表として渡され、他の描画やディスパッチの状態に漏れ出しません。
GPU側のstorageバッファはCPUから直接読めません。結果を確認するには、copyBufferToBufferでMAP_READ用の中間バッファへコピーし、mapAsyncで非同期にマップしてからgetMappedRangeで読み出す、という手順が必要です。この非同期性のため、計算結果の取得は原理的にコマンド投入から1フレーム以上遅延します。
機械学習推論とレンダリングでの実用
ブラウザ上のニューラルネットワーク推論(ONNX Runtime WebのWebGPUバックエンドなど)は、行列積・畳み込み・活性化関数をそれぞれコンピュートシェーダとして実装し、レイヤーごとにディスパッチを連ねる形で構成されます。WebGLでも同様の推論は可能でしたが、テンソルをテクスチャに偽装する変換コストと、任意形状のバッファへ自由に書き込めない制約が性能上の足かせでした。WebGPUはテンソルをstorageバッファへそのまま置けるため、レイアウト変換の手間が減ります。
レンダリング分野では、パーティクルの物理更新・GPUベースのカリング(描画対象の事前選別)・ポストプロセス(ブラー、被写界深度)がコンピュートシェーダの主戦場です。頂点シェーダで計算するには不自然な「フレームをまたぐ状態更新」を、コンピュートシェーダとストレージバッファの組み合わせで自然に表現できます。計算結果をそのまま次のレンダーパイプラインの頂点バッファとして再利用すれば、CPUへ結果を戻さずGPU内で完結する処理チェーンが組めます。
| 観点 | WebGLでのGPGPU | WebGPUのコンピュートシェーダ |
|---|---|---|
| 実行経路 | フラグメントシェーダへの偽装 | 専用のGPUComputePipeline |
| 出力先 | テクスチャ/フレームバッファのみ | 任意サイズのstorageバッファ |
| 並列単位 | ピクセル(暗黙) | ワークグループ(明示的に指定) |
| リソース結合 | グローバルなbind状態 | バインドグループで局所化 |
| 検証タイミング | 描画の都度 | パイプライン構築時に前倒し |
呼び出し側のJSはメインスレッドで完結しますが、大量データの準備や結果の後処理をWebワーカーへ逃がせば、GPUディスパッチの合間もUIスレッドを塞ぎません。WebGPU自体はGPU側の並列性を扱う技術であり、CPU側のスレッド分割とは独立した話である点を区別しておくと設計判断を誤りません。
「コンピュートシェーダ」は描画パイプラインを介さない汎用GPU計算の実行単位、「ワークグループ」はその並列実行の単位グループ、「バインドグループ」はシェーダとリソースを結ぶ明示的な対応表です。WebGLのGPGPUがテクスチャへの偽装だった一方、WebGPUはstorageバッファへの直接読み書きとパイプライン構築時の型検証によって、これを正式な機能として提供します。
Web/フロントエンド Article
WebGPUの計算シェーダを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
WebGPU
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 6
導入後に効く点
WGSLはCPU/GPU間の型を静的に固定する言語で、バッファはバインドグループを介して明示的にシェーダへ結びつける。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 6
判断チェックリスト
- 自社の用途が「WebGPU / WGSL」に近いか確認する。
- 強みである「コンピュートシェーダは描画パイプラインを介さず、ワークグループ単位でGPUコアに計算を並列分配する専用の実行経路。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。