NTTドコモR&Dの技術ブログです。

今年ホットなWebGPUのその凄さを体感してみよう!!

はじめに

こんにちは!NTTドコモクロステック開発部の藤枝です!
この記事は、NTTドコモ R&D Advent Calendar 2023 23日目の記事です。 クリスマスの記事ということで、まずは皆様今年もお疲れ様でした! 今年も残すところ残り僅かというところで、あと少し乗り越えて年末年始を楽しみましょう!! 私のアドベントカレンダーは2回目ということでよろしければ、過去記事を載せておきますのでご覧ください!

nttdocomo-developers.jp

私の普段の業務は、XR機器における分散処理技術の技術領域を担当しており、上記記事は全くそちらに関係が無いように思われと思いますが、 新しい技術や自分の分野と少し違った分野の技術に触れていきたい!といったモチベーションでアドベントカレンダーは普段触らない様な技術に触れていきたいと思っています! そこで、今年は何にしようかと迷っており、何があったかと振り返っていたのですが重大なことが起きていたことに気づきました。 それは・・・・

「WebGPU」の正式リリースです!!

www.w3.org developer.mozilla.org

WebGPU?それ初めて聞いたという方も多くいらっしゃると思います。 そこで、今回はWebGPUについて僕も初めて使いますが、皆さんと一緒に知っていければと思います!
では、さっそく・・・スタートです!!

本記事の対象となる方

  • 3Dアプリケーションにご興味がある方
  • ホットな技術に触れてみたい!と思われる方
  • WebGPU何それと思われる方

かなりふんわりしていますが、上記の方がご覧になるとより楽しめる記事となっております! 僕も一回も使ったことないので一緒に見ていき触っていきましょう!
※素人のため厳密な表現が違う可能性がございますがご容赦ください(´;ω;`)

WebGPU何それ

WebGPU??初めて聞いたという方もいらっしゃるかと思います。 WebGPUは、今年の5月2日にChrome 113にて正式リリースされたWebGLの後継のブラウザでサポートされるグラフィックAPIです。 グラフィックAPIというとゲームをされる方は耳にしたことがあるかもしれませんが、DirectXやVulkan、そしてMetalなどがこれらに該当します。

www.vulkan.org developer.apple.com

では、それらとWebGPUで何が大きく違うかというと、「Webで動作する」というところです。
本来、3Dの描画ではCPUが行う汎用計算では不得意とされる描画計算を行うので、アクセラレータが必要となります。 そのアクセラレータがいわゆるGPU(Graphic Processing Unit)です。 最近のコンシューマゲーム機にはほぼこのGPUが搭載されています。そこで、普段プログラムを書く人がCPUを利用して命令を実行しているのに対して、 3Dの描画ではこのGPUに処理をお願いする必要があり、プログラムの実装においてGPUをより簡単に利用可能とするのがグラフィックAPIです。(全く簡単ではないのですが笑) つまり、特殊なハードウェアを操作するためのAPIというわけです。ここでようやくWebGPUが登場します。 WebGPUもグラフィックAPIのためこの役割は変わりませんが、ブラウザというアプリケーション上で動作する点が大きく異なります。 DirectXやVulkan、Metalはネイティブアプリで利用されることを想定していますが、ブラウザはOSから見ると1アプリケーションでありその上で動作するJavasript等のスクリプトはブラウザがインタプリタに近い立場となって解釈、実行されます。 そのため、ブラウザで動作するグラフィックAPIはGPUを操作可能であるという要件に加えて、ブラウザが持つ描画エンジン等との兼ね合いもとる必要があります。

WebGPUとグラフィックAPIの関係性
(MDN WebGPU APIより:WebGPU API - Web API | MDN
そこで、誕生したのがWebGLやその後継のWebGPUというわけです。 さて、ではWebGLとWebGPUでは何が異なるのでしょうか?それを次の章でお話していきたいと思います。 WebGPUを直観でまず見てみたいという方は以下をご覧ください!

webgpu.github.io

WebGPUが生まれた理由

一言でいうと、時代の変化に追従した形となります。
ただ、WebGPUが生まれた理由を細かくお話するためには、これまでのGPUとグラフィックAPIの在り方の変化について知る必要があります。 OSをはじめとしてソフトウェアは抽象化いわゆるレイヤに細かく切ることで、それぞれの機能を多段に疎結合にすることで扱いやすくしてきました。 グラフィックAPIも当然GPUというアクセラレータを利用するため、ユーザにとって扱いやすく、開発者がアップデートしやすいようにしてきました。 そうすると、何が起きるか?というと、ソフトウェア部分のオーバヘッドが大きくなり、+GL系のグラフィックAPIは非同期通信であるために非効率さが出てきます。 また、スマートフォンなどで利用されるArmをはじめとして、SoC(System On Chip)の台頭により、従来想定されてきた計算機とは全く異なる計算機を考慮できるはずもないので、これらはよりカオスになってきます。 そうした背景から、上記の課題解決をしようとする動きがあり考案されたのが、レイヤを薄くしてオーバヘッドを少なくしてやろう・APIとしてよりGPUの性能を引き出すチューニングがしやすい設計にしてやろうというものです。 上記から、結論としてWebGLが主流だった時代からWebGPUが生まれた理由は、よりレイヤを薄くすることで低レベルなGPUへのアクセスを可能としGPUというハードウェアの性能を十分に引き出してやろうということになります。

御託はいいので早くWebGPUを触ろう

ここまでで3000字を超え、見ている人も書いている人(あ、私でした)もしんどくなってきたかと思うので、さっそく実装に移っていきましょう。
本記事では触りの部分に触れ、グラフィックAPIのハンズオンで王道な真っ黒の画面を映すところをやっていきましょう。 以下、htmlファイルを作成しましょう!index.htmlという名前でファイルを作成し、テキストエディタで書いてみてください!

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>WebGPU Life</title>
  </head>
  <body>
    <canvas width="512" height="512"></canvas>
    <script type="module">
      // キャンバス要素を選択
      const canvas = document.querySelector("canvas");

      // ブラウザがWebGPUをサポートしているか確認
      if (!navigator.gpu) {
         throw new Error("WebGPU not supported on this browser.");
      }

      // GPUアダプタをリクエスト
      const adapter = await navigator.gpu.requestAdapter();
      if (!adapter) {
        throw new Error("No appropriate GPUAdapter found.");
      }

      // デバイスをリクエスト
      const device = await adapter.requestDevice();

      // WebGPUコンテキストを取得
      const context = canvas.getContext("webgpu");

      // 優先されるキャンバスフォーマットを取得
      const canvasFormat = navigator.gpu.getPreferredCanvasFormat();

      // コンテキストを設定
      context.configure({
        device: device,
        format: canvasFormat,
      });

      // コマンドエンコーダを作成
      const encoder = device.createCommandEncoder();

      // レンダーパスを開始
      const pass = encoder.beginRenderPass({
        colorAttachments: [{
          view: context.getCurrentTexture().createView(),
          loadOp: "clear",
          clearValue: { r: 0, g: 0, b: 0.4, a: 1 },
          storeOp: "store",
        }]
      });

      // レンダーパスを終了
      pass.end();

      // コマンドバッファを完成させる
      const commandBuffer = encoder.finish();

      // コマンドバッファをキューにサブミット
      device.queue.submit([commandBuffer]);

      // 追加のコマンドバッファをキューにサブミット
      device.queue.submit([encoder.finish()]);
    </script>
  </body>
</html>

簡単に言うと、htmlのCanvasタグにWebGPUによって描画された結果を書き書きしているコードになります。 結果は下記のようになるかと思います!

まとめ

今回の記事では、WebGPUの生い立ちと簡単なサンプルの動作確認を行ってみました。 まだまだ、やり足りないというお気持ちがあるかと思います。 そういった方はクリスマスと年末年始の宿題として、ぜひ色々触ってみて拡張してみていってください! 前段の部分が長すぎて実装のところが少し少なくなってしまいましたが、本記事は以上となります! 皆様、良いお年をお過ごしください!