
-
WebGPUでGPUコンピューティングのパワーを最大化
WebGPU APIを使用して、グラフィックスや一般的な演算でGPUデバイスに安全にアクセスする方法を学びましょう。WGSLシェーディング言語を使ってGPUプログラムを作成する方法についても説明します。すべてのデスクトップとモバイルデバイスの電力消費を最小限に抑えつつ最適なパフォーマンスを実現するためのベストプラクティスも紹介します。
関連する章
- 0:00 - イントロダクション
- 2:14 - WebGPU APIの詳細
- 9:54 - シェーダの開発
- 13:57 - パフォーマンスの最適化
リソース
- babylon.js – webGL and WebXR library
- Metal Performance Shaders
- three.js – webGL and WebXR library
- Transformers.js - Huggingface
- WebGPU - W3C
- WebGPU Samples - github.io
関連ビデオ
WWDC25
WWDC20
-
このビデオを検索
こんにちは Safari Teamのエンジニア Mikeです 本日はWebGPUを使って WebからGPU上の並列演算を 実現する方法について説明します WebGPUは3Dグラフィックスに関して WebGLと同じことができますが パフォーマンスと柔軟性は はるかに優れています Web上のグラフィックスに最適です また ブラウザを使いGPU上で 汎用的な演算を行う 唯一の方法でもあります Metalに詳しい方であれば すぐに馴染めるはずです ほとんどの呼び出しはMetalフレームワーク 呼び出しと1対1の対応関係があります 実際にはMac、iPhone iPad、Vision Proなど Metalに対応するすべての プラットフォームでサポートされています Web APIであるため WebGPUを使う WebサイトやWeアプリは 対応する環境であれば どこでも動作します Apple以外のシステムでは WebGPUは Metalに似たAPIで実装されます 低レベルのグラフィックスプログラミングに 詳しくなくても WebGPUをサポートする多くの グラフィックスライブラリを使えば WebGPUが提供する パフォーマンスや機能をすべて 利用できます WebGPUを内部で実行する threeJSを使えば これらの美しい3Dクラゲをリアルタイムで アニメーション化できます これは素晴らしい例です Safariで非常にスムーズに動作します 最新ハードウェアを最大限に 活用できるように WebGPUが 一から構築されているためです まずAPIを調べ WebGPUとMetalとの対応を確認します ここではWebGPUアプリに必要な多くの コードについて順を追って説明します 次に GPU上で直接実行されるコード WebGPUシェーダプログラムの 作成方法を説明します シェーディング言語とWeb用に新しい 言語が 必要な理由について説明します
基本を説明した後で APIで最高のパフォーマンスを 実現する方法を説明します すでにWebGPUに詳しい方であれば Appleプラットフォーム向けの特定の 最適化について説明しているので 特に興味深い内容となるでしょう ではグラフィックスパイプラインを 簡単に見ていきましょう
パイプラインは左から右に流れると 考えることができます まずWebサイトやWebアプリで 画像 ビデオ バイナリデータなどの コンテンツを読み込みます
そのコンテンツはWebKitに渡され そこでGPUで使えるように準備されます
WebKitはMetalフレームワークを呼び出し 後でグラフィックスハードウェア上で直接 実行されるリソースやプログラムを 作成します
では少し詳しく見ていきましょう WebGPUでは Metalは3種類のリソース バッファ テクスチャ サンプラを生成します これらのリソースはWebKitによって GPUバインドグループにまとめられます 基本的にはリソースをグループ化して GPUで効率的に使うための 構造化された方法です 内部的にはこれらのリソースはすべて 引数バッファにまとめられます このバッファは実際のGPUリソースへの 参照を保持するMetalバッファです プログラム自体はコードの文字列から 生成され 主に3つのタイプのプログラム 演算 頂点 フラグメントに コンパイルされます これらはGPU上で実行される 実際の命令であり 計算から画面上のピクセルの レンダリングまですべてを実行します そこでリソースとプログラムがパイプライン にどう組み込まれるかを十分理解した上で WebGPUがAPIで さまざまなインターフェイスをどのように 定義するかについて簡単に説明します
WebGPUはフラットAPIですが 多くのインターフェイスを持っています 階層の最上位には GPUオブジェクトインターフェイスと GPUアダプタインターフェイスがあります
canvasはWebGPUでよく使われます canvasはWebGPUのコンテキストを照会 することでGPUCanvasContextを返します
デバイスはほとんどのAPI呼び出しの メインエントリポイントです これは他のほとんどのインターフェイスを 作成するために使われます
APIにはさまざまなインターフェイスが ありますが それらはいくつかのカテゴリに 整理されています テクスチャ バッファ サンプラなどの リソースや
リソースに対してコマンドを 発行するエンコーダ エンコーダでのリソースの解釈方法を 指示するパイプライン 関連リソースをグループ化する バインドグループ GPUで計算を実行するための命令を含む シェーダモジュール といったカテゴリがあります ではWebGPUの 全体構造を理解したところで デバイスとリソースの作成方法を示して APIの使い方を説明します
デバイスはほとんどのAPI呼び出しの エントリポイントです Metalに詳しい方であれば MTLDeviceと似ていることがわかるでしょう
ページにcanvasがあると仮定して まずcanvas要素を取得します 次にnavigator.gpu.requestAdapter() を使ってアダプタを作成し requestDeviceを呼び出して GPUデバイスを作成します
WebGPUはいくつかの拡張機能を サポートしており その一つが半精度浮動小数点数を使うための shader-f16拡張機能です
これはメモリ帯域幅を削減することで パフォーマンスを向上させます すべてのAppleデバイスでサポートされて いますが オプション機能であるため 他のプラットフォームで使う際は 事前にサポート状況を確認してください
次にconfigureを呼び出し デバイスで canvasのコンテキストを設定します これによりcanvasがメモリにリンクされ GPUはこのメモリに書き込みを行います
デバイスの準備ができたので リソースの作成を開始できます WebGPUではバッファやテクスチャなどを 使って処理を行います MetalではこれらはMTLBufferと MTLTextureで表されます
バッファは非常に柔軟性が高く 浮動小数点数のベクトルといった 単純なものから 自分で定義した より複雑なカスタムデータ型まで あらゆる種類のデータを保存できます たとえばパーティクル型の 複数のインスタンスを保持する バッファがあるとします バッファには3つのパーティクルが 保存されていると考えてみましょう
バッファはデバイスでcreateBufferを 呼び出すことで作成されます バッファサイズと使用モードを渡します 使用モードによりWebGPUは データ競合を回避できます APIを追加して複雑にする 必要はありません
デバイスにはqueueというプロパティがあり バッファやテクスチャの操作に使います
バッファが作成されたら writeBufferを呼び出し バッファ オフセット JavaScriptの arrayBufferを渡し その内容を設定します
バッファと同様にテクスチャも 基本的にはメモリの塊ですが GPU上の特別なテクスチャレジスタや 命令と関連付けられます 多くの場合 これらは画像データを表し 1次元 2次元 2次元テクスチャの配列 6つの2次元テクスチャの配列である キューブマップ または3次元テクスチャとして 扱われます
テクスチャを作成するには device.createTextureを呼び出し テクスチャの幅と高さ 2Dテクスチャフォーマット および使用モードを渡します
GPUTextureの作成後 device.queue.copyExternal ImageToTextureを使って 画像ビットマップ 作成した2Dテクスチャ 画像サイズを渡すことで 画像データを読み込むことができます
多くの場合テクスチャは画像データから 作成されGPU上で画像を表します デバイスとリソースを作成したら パイプラインの作成方法を見てみましょう
パイプラインはGPUでのテクスチャや バッファの使用方法を指定します パイプラインには2つの種類があります レンダリングパイプラインは頂点プログラム やフラグメントプログラムで使われ 演算パイプラインは演算プログラムで 使われます これらはMetalの MTLRenderPipelineStateオブジェクトと MTLComputePipelineState objects オブジェクトにマップされます
演算パイプラインを作成するには device.createComputePipelineを呼び出し バインドグループまたは シェーダからレイアウトを生成する 定数識別子autoを渡します
レイアウトとはバッファ テクスチャ サンプラが APIからGPUプログラムへ渡される 構造化された方法です
パイプラインの作成には シェーダモジュールが必要です これは文字列から作成されます
レンダリングパイプラインも 同様の方法で作成され 自動レイアウト 頂点シェーダモジュール フラグメントシェーダモジュールが 使われます
デバイス リソース パイプラインを 作成したら WebGPUアプリケーションの基本設定は 完了です
WebGPU APIのアーキテクチャについて 説明してきました 次にシェーダの開発方法を見てみましょう
WGSLと呼ばれる WebGPUシェーディング言語を使うと WebサイトではGPU上で直接実行される プログラムを簡単に作成できます AppleはWGSLシェーディング言語の 設計と実装に深く関わっています WGSLはWebでの安全を考慮し 一から構築されています WGSLは3種類のプログラムに 対応しています 頂点プログラムとフラグメントプログラム 演算プログラムです
この簡単なWebGPUの例の作成方法を 説明します 次のプログラムで構成されます JavaScriptからバッファデータを取得し 画面に三角形を作成する頂点プログラム テクスチャの個々の色と深度の値を計算する フラグメントプログラム そして一般的な演算を実行できる 演算プログラムです この演算プログラムでは 物理シミュレーションを実行します
頂点プログラムは三角形が表示される 画面上の位置を計算します
ここではこの例で使われている 10万個の三角形の輪郭を確認できます
三角形の出力位置を書き込むには @builtin位置属性を使います
これはmain関数の定義と 頂点シェーダの入力です 位置と色を書き込むだけです ではフラグメントシェーダを見てみましょう
頂点ステージで生成した色を取得し その色をテクスチャに保存します これは簡単な例ですが ここに任意のロジックを挿入して 色と深度の値を計算できます ストレージテクスチャやバッファへの 書き込み アトミック操作なども実行できます WGSLは本当に柔軟です その柔軟性をさらに見てみましょう 演算シェーダを取り上げます
他のプログラムと同様に 演算シェーダには JavaScriptからシェーダへの入力である 多くのバインディングを含めることが できます
演算シェーダは非常に便利です 必要な演算を実行し 結果をバッファに保存し バッファをJavaScriptコードに 読み戻すことができます 画面上に描画する必要はありません WebGLでは演算シェーダを 使えませんでした これが新しいアプリケーションで WebGPUを使うべきもう一つの理由です
演算プログラムには 演算シェーダが実行されるグリッドの サイズを定義するワークグループサイズ が必要です
グリッド全体における位置を示す global_invocation_idも使います これは組み込み変数であり JavaScriptから何も渡さなくても使えます
演算シェーダの本体は 重力 速度 経過時間を適用して パーティクルシミュレーションを更新します
演算シェーダでは必要な演算を すべて実行できます この演算はGPU上で驚異的な パフォーマンスで並列実行されます
パーティクルが完全にフェードアウトすると 確率マップに対して textureLoadを呼び出し パーティクルの新しい位置を 選択することで そのパーティクルが再生成される 新しい位置が決まります
最後にパーティクル残りの属性が 開始値にリセットされ パーティクルはバッファに保存されます
すべてを組み合わせると WebGPUロゴの 素敵なアニメーションが完成します GPUの並列処理能力を活用することで これまでWebではできなかった どのような規模の演算でも リアルタイムのパフォーマンスを 維持しながら実行できます
素晴らしいですね
WebGPUアプリケーション用のシェーダの 開発方法に関する概要を説明しました 次に WebGPUの最高のパフォーマンスを 引き出す方法を説明します
Appleのプラットフォームで 最高の体験を実現するために役立つ 覚えておくべきガイドラインがあります 優れたパフォーマンスの鍵は メモリ使用量に注意することです つまりメモリ効率の良いデータ型を使い レンダリングコマンドを 一度記録して再利用し リソースの数を低く抑えるということです さらに詳しく見ていきましょう
メモリ使用量を最小限に抑えるには いくつか方法があります まず半精度浮動小数点数を使います これはIEEE標準です WGSLではデータ型はf16と呼ばれます これによりメモリ使用量が大幅に削減され パフォーマンスが向上します しかし 必ずしも実用的ではありません 精度が低くてもアルゴリズムが 安定していることを確認し より大きな値を処理できる 32ビット浮動小数点数とは異なり その値が65,000強で最大になることに 注意してください 特にiOSとvisionOSでは データをf16や圧縮形式で保存すると メモリ不足によりプログラムが 終了するのを 防ぐことができます 半精度浮動小数点を使うには デバイス作成時とWGSLコードで 有効にする必要があります 簡単なコード例を使って その方法を説明します
まずrequestDeviceの呼び出しで shader-f16拡張機能を有効にして シェーダに「enable f16」 ステートメントを追加します
その後でf16スカラ型とベクトル型を 先ほどと同様にすべての32ビット型と共に 使うことができます データを半精度で保存し すぐにf32にパック解除しても 多くのメモリのメリットを活かして メモリ不足によりアプリの終了を 回避できます
メモリ使用量を最小限に抑える もう一つの方法は 不要なバッファやテクスチャの 更新呼び出しを回避することです これを行うには Metalリソースをサポートするメモリへの JavaScriptからのデータコピーが必要です インデックスモードや間接使用モードでの バッファ更新は 高いコストがかかる場合があります バッファの再利用前に 検証の実行が必要になるためです これらのバッファは頂点バッファを 直接的/間接的にインデックス参照しており WebGPUでは描画コマンドの実行前に バッファへのすべてのオフセットが 範囲内にあることを確認する必要があります
こうしたバッファは必要な場合にのみ 更新してください これは書き込みまたは読み取り/書き込み アクセスで バインドグループ内のバッファを使う際にも 該当します コード例のようにシェーダ内のリソースを 使って書き込まないのであれば 特にリソースがインデックスや間接バッファ の場合は 読み取り専用アクセスを優先してください メモリに関するこれらのヒントは Appleのプラットフォームだけでなく すべてのモバイルやデスクトップデバイスで パフォーマンスに大きな影響を与えます 次にレンダリングコマンドの 再利用について詳しく説明します
再利用の方法にレンダリングバンドルが あります コマンドを1回エンコードしておけば 必要なだけ何度でも再利用できます WebGPUではすべての読み書きが適切に 定義され 範囲内にあることを確認する 必要があります 通常であれば各フレームで多数の検証が 行われます レンダリングバンドルでは 検証はバンドル作成時の1回だけで バンドル実行時に毎回実行されません このため 処理時間の短縮や ネイティブに近いパフォーマンスが実現され 実際のロジックに余裕が生まれます レンダリングバンドルは簡単に作成できます レンダリングバンドルエンコーダを作成し 次に描画関数の呼び出しをエンコードします レンダリングパスエンコーダと同じです finish()を呼びして 再利用向けのバンドルを作ります
バンドルができたので executeBundles()を呼び出して すべての描画コマンドを実行できます 必要に応じて何度でも実行できます
内部的にレンダリングバンドルは Metalの間接コマンドバッファにマップされ パフォーマンスが同様に改善されます メモリ使用量の問題を解決し 検証のオーバーヘッドを削減したので リソース数の削減について見てみましょう
具体的には コマンドバッファや レンダリングパスと演算パス バインドグループレイアウトと バインドグループです
コマンドバッファの境界では 高速なオンチップメモリと 統合されたオンデバイスメモリ間の 同期が必要です 可能であれば更新ループごとに 1つのコマンドバッファを使うか それが不可能な場合は 一般的な経験則として コマンドバッファの数を できるだけ少なくします コマンドバッファを分割する必要があるのは 統合メモリに書き戻されたデータが 必要になる場合だけです 通常 そのようなケースはまれです
コマンドバッファとは異なり パスは統合メモリとの同期を必要としません それでもレンダーターゲットや 演算ディスパッチのサイズによっては かなりのメモリ帯域を消費します そのためメモリ帯域幅を節約するには 可能な限り少ないパスを使うのが適切です
多くの携帯電話と同様にAppleデバイスの GPUは タイルベースの遅延レンダラに 基づいています パスの統合やメモリ帯域の節約のための ベストプラクティスによって サイトやWebアプリはAppleのハードウェア上 で優れた性能を発揮できます タイルベースのレンダラの詳細については 「Metal Performanceを Appleシリコン搭載のMacに最適化する」と WWDC 2020の「Apple GPUと Metalの併用」をご覧ください
バインドグループに注目してみましょう バインドグループはMetalの引数バッファで 実装されており バインドグループの作成と同時に 新しいMTLBufferも作られます 動的オフセットを使うと 同じレイアウトを共有するが 実行時に異なるリソースを使う単一の バインドグループを作成できます 動的オフセットを使うには シェーダモジュール からの自動レイアウトを使わずに カスタムのバインドグループレイアウトを 作る必要があります
hasDynamicOffsetを指定し createBindGroupLayoutを呼び出して レイアウトを作り 次にそのレイアウトを 渡してバインドグループを作ります 動的オフセットはsetBindGroupの 呼び出しに関係します バインドグループの動的バッファごとに 1つのオフセットが必要です
このバインドグループで動的オフセットを 使うバッファは1つであるため 1つのオフセットがsetBindGroupに 渡されます
たとえば10個のバインドグループを作り 各 グループが64バイトのバッファを持つよりも
640バイトのバッファを1つ作って10個分の 64バイトオブジェクトを表す方が適切です これでMetalバッファを9個作らずに 済みました
類似したデータを少ないメモリに保存し 検証の繰り返しを避け 作られるMetalオブジェクトの総数を最小に 抑えることで WebGPUで魅力的かつ 高効率なWebサイトやWebアプリを作れます WebGPUを使う際は これらパフォーマンスの 考慮事項を意識してください WebGPUを使うとカスタムアルゴリズムを GPU上で直接実行できます これまでWebからは不可能でした 今日からWebGPUをお使いください Mac、iPhone、iPadと Vision Proで動作します 最適な使用ガイドラインもご参照ください
WebでのGPUプログラミングの未来に 期待しています
-