WebGL デベロッパーにとって、WebGL の後継であり、最新のグラフィック API をウェブに導入する WebGPU の使用を開始するのは、恐怖と興奮の両方を感じるかもしれません。
WebGL と WebGPU は多くのコアコンセプトを共有しています。どちらの API でも、シェーダーと呼ばれる小さなプログラムを GPU 上で実行できます。WebGL は頂点シェーダーとフラグメント シェーダーをサポートしていますが、WebGPU はコンピューティング シェーダーもサポートしています。WebGL は OpenGL シェーディング言語(GLSL)を使用し、WebGPU は WebGPU シェーディング言語(WGSL)を使用します。2 つの言語は異なりますが、基本的なコンセプトはほとんど同じです。
本記事では、WebGL と WebGPU の違いをいくつか取り上げ、使い始める際の参考にしていただければ幸いです。
グローバル状態
WebGL には多くのグローバル状態があります。一部の設定は、バインドされるテクスチャやバッファなど、すべてのレンダリング オペレーションに適用されます。このグローバル状態は、さまざまな API 関数を呼び出して設定します。この状態は、変更するまで有効です。WebGL のグローバル状態は、グローバル設定を変更し忘れやすいため、エラーの主要な原因となります。さらに、グローバル状態により、コードの共有が困難になります。開発者は、コードの他の部分に影響する方法でグローバル状態を誤って変更しないように注意する必要があるからです。
WebGPU はステートレス API であり、グローバルな状態を維持しません。代わりに、パイプラインのコンセプトを使用して、WebGL でグローバルだったすべてのレンダリング状態をカプセル化します。パイプラインには、使用するブレンド、トポロジ、属性などの情報が含まれます。パイプラインは不変です。一部の設定を変更するには、別のパイプラインを作成する必要があります。また、WebGPU では、コマンド エンコーダを使用してコマンドをバッチ処理し、記録された順に実行します。これは、オブジェクトを 1 回通過するだけで、ライトのシャドウマップごとに 1 つずつ、複数のコマンド ストリームをアプリケーションが記録できるシャドウ マッピングで役立ちます。
まとめると、WebGL のグローバル状態モデルでは、堅牢でコンポーザブルなライブラリとアプリケーションの作成が困難で脆弱でしたが、WebGPU では、デベロッパーが GPU にコマンドを送信する際に追跡する必要のある状態の量が大幅に削減されました。
これ以上同期しない
GPU では、パイプラインをフラッシュしてバブルが発生する可能性があるため、通常、コマンドを送信して同期的に待機することは非効率的です。これは、マルチプロセス アーキテクチャを使用し、GPU ドライバが JavaScript とは別のプロセスで実行される WebGPU と WebGL で特に当てはまります。
たとえば、WebGL で gl.getError()
を呼び出すには、JavaScript プロセスから GPU プロセスへの同期 IPC とその逆が必要です。これにより、2 つのプロセスが通信する際に CPU 側でバブルが発生することがあります。
このようなバブルを回避するため、WebGPU は完全に非同期に設計されています。エラーモデルとその他のオペレーションはすべて非同期で発生します。たとえば、テクスチャを作成すると、テクスチャが実際にはエラーであっても、オペレーションはすぐに成功したように見えます。エラーは非同期でのみ検出できます。この設計により、プロセス間通信のバブルが発生せず、アプリケーションの信頼性の高いパフォーマンスが実現します。
コンピュート シェーダー
コンピューティング シェーダーは、GPU 上で実行され、汎用計算を行うプログラムです。これらは WebGL ではなく、WebGPU でのみ使用できます。
頂点シェーダーやフラグメント シェーダーとは異なり、グラフィック処理に限定されず、機械学習、物理シミュレーション、科学コンピューティングなど、さまざまなタスクに使用できます。コンピューティング シェーダーは数百、数千ものスレッドによって並列に実行されるため、大規模なデータセットの処理に非常に効率的です。GPU コンピューティングの詳細については、WebGPU に関する詳細な記事をご覧ください。
動画フレーム処理
JavaScript と WebAssembly を使用してビデオ フレームを処理することには、いくつかの欠点があります。GPU メモリから CPU メモリにデータをコピーするコストと、ワーカーと CPU スレッドで実現できる並列処理の制限です。WebGPU にはこのような制限がなく、WebCodecs API と緊密に統合されているため、動画フレームの処理に適しています。
次のコード スニペットは、VideoFrame を WebGPU で外部テクスチャとしてインポートして処理する方法を示しています。こちらのデモをお試しください。
// Init WebGPU device and pipeline...
// Configure canvas context...
// Feed camera stream to video...
(function render() {
const videoFrame = new VideoFrame(video);
applyFilter(videoFrame);
requestAnimationFrame(render);
})();
function applyFilter(videoFrame) {
const texture = device.importExternalTexture({ source: videoFrame });
const bindgroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: texture }],
});
// Finally, submit commands to GPU
}
デフォルトでアプリケーションのポータビリティ
WebGPU では、limits
をリクエストする必要があります。デフォルトでは、requestDevice()
は物理デバイスのハードウェア機能と一致しない GPUDevice を返しますが、すべての GPU の妥当かつ最小の公分母を返します。デベロッパーにデバイスの上限をリクエストするよう義務付けることで、WebGPU はアプリケーションが可能な限り多くのデバイスで実行されるようにします。
キャンバスの処理
WebGL コンテキストを作成して、alpha、antialias、 colorSpace、depth、preserveDrawingBuffer、stencil などのコンテキスト属性を指定すると、WebGL はキャンバスを自動的に管理します。
一方、WebGPU ではキャンバスを自分で管理する必要があります。たとえば、WebGPU でアンチエイリアスを実現するには、マルチサンプル テクスチャを作成してレンダリングします。次に、マルチサンプル テクスチャを通常のテクスチャに解決し、そのテクスチャをキャンバスに描画します。この手動管理により、1 つの GPUDevice オブジェクトから任意の数のキャンバスに出力できます。一方、WebGL ではキャンバスごとに作成できるコンテキストは 1 つだけです。
WebGPU マルチキャンバスのデモをご覧ください。
なお、現在、ブラウザではページあたりの WebGL キャンバスの数に上限があります。執筆時点では、Chrome と Safari では最大 16 個の WebGL キャンバスしか同時に使用できません。Firefox では最大 200 個のキャンバスを作成できます。一方、ページあたりの WebGPU キャンバスの数に制限はありません。
役立つエラー メッセージ
WebGPU は、API から返されるすべてのメッセージのコールスタックを提供します。つまり、コード内でエラーが発生した場所をすばやく確認できるため、デバッグやエラーの修正に役立ちます。
WebGPU のエラー メッセージは、コールスタックを提供できるだけでなく、わかりやすく、対処も容易です。エラー メッセージには通常、エラーの説明とエラーの修正方法の提案が含まれます。
WebGPU では、各 WebGPU オブジェクトにカスタムの label
を指定することもできます。このラベルは、GPUError メッセージ、コンソール アラート、ブラウザのデベロッパー ツールでブラウザによって使用されます。
名前からインデックスへ
WebGL では、多くのものが名前でつながっています。たとえば、GLSL で myUniform
というユニフォーム変数を宣言し、gl.getUniformLocation(program, 'myUniform')
を使用してその場所を取得できます。これは、ユニフォーム変数の名前を間違えて入力するとエラーになるので便利です。
一方、WebGPU では、すべてがバイトオフセットまたはインデックス(多くの場合「ロケーション」と呼ばれます)で完全に接続されています。WGSL と JavaScript のコード位置を同期するのはお客様の責任となります。
Mipmap の生成
WebGL では、テクスチャのレベル 0 mip を作成してから gl.generateMipmap()
を呼び出すことができます。WebGL により、他のすべての mip レベルが生成されます。
WebGPU では、ミップマップを自分で生成する必要があります。これを行う組み込み関数はありません。この決定について詳しくは、仕様に関するディスカッションをご覧ください。webgpu-utils などの便利なライブラリを使用して MIP マップを生成することも、自分で作成する方法を学ぶこともできます。
ストレージ バッファとストレージ テクスチャ
ユニフォーム バッファは WebGL と WebGPU の両方でサポートされており、サイズが制限された定数パラメータをシェーダーに渡すことができます。ストレージ バッファはユニフォーム バッファによく似ていますが、WebGPU でのみサポートされており、ユニフォーム バッファよりも強力で柔軟です。
シェーダーに渡されるストレージ バッファのデータは、ユニフォーム バッファよりもはるかに大きくなることがあります。仕様では、均一バッファ バインディングのサイズは最大 64 KB とされています(
maxUniformBufferBindingSize
を参照)。一方、WebGPU ではストレージ バッファ バインディングの最大サイズは 128 MB 以上です(maxStorageBufferBindingSize
を参照)。ストレージ バッファは書き込み可能で、一部のアトミック オペレーションをサポートしますが、均一バッファは読み取り専用です。これにより、新しいクラスのアルゴリズムを実装できます。
ストレージ バッファ バインディングは、より柔軟なアルゴリズムのためにランタイム サイズの配列をサポートしていますが、ユニフォーム バッファ配列のサイズはシェーダーで指定する必要があります。
ストレージ テクスチャは WebGPU でのみサポートされており、テクスチャに対するストレージ バッファとユニフォーム バッファの違いです。通常のテクスチャよりも柔軟性に優れ、ランダム アクセスの書き込み(および将来の読み取り)をサポートします。
バッファとテクスチャの変更
WebGL では、バッファまたはテクスチャを作成してから、gl.bufferData()
と gl.texImage2D()
を使用してサイズをいつでも変更できます。
WebGPU では、バッファとテクスチャは変更できません。つまり、作成後にサイズ、使用状況、形式を変更することはできません。変更できるのはコンテンツのみです。
スペースの規則の違い
WebGL では、Z クリップ空間の範囲は -1~1 です。WebGPU では、Z クリップ空間の範囲は 0~1 です。つまり、z 値が 0 のオブジェクトはカメラに最も近く、z 値が 1 のオブジェクトは最も遠くになります。
WebGL では、Y 軸が上、Z 軸が視聴者側を向くという OpenGL の規則が使用されます。WebGPU では、Y 軸を下向きに、Z 軸を画面の外側に配置するメタル規則を使用します。なお、フレームバッファ座標、ビューポート座標、フラグメント/ピクセル座標において、Y 軸の方向は下になります。クリップ空間では、WebGL と同様、Y 軸の方向は上のままです。
謝辞
この記事をレビューしてくれた Corentin Wallez、Gregg Tavares、Stephen White、Ken Russell、Rachel Andrew に感謝します。
また、WebGPU と WebGL の違いについて詳しくは、WebGPUFundamentals.org もおすすめします。