WebGPUの紹介

この記事ははてなエンジニア Advent Calendar 2019 - Qiitaの4日目の記事です。昨日はid:yigarashiさんのApollo ClientのInMemoryCacheとMutationに関する調査・考察 - yigarashi のブログでした。

こんにちは、はてなでWebアプリケーションエンジニアをしているid:maku693です。

普段はマンガチームの一員としてWebアプリケーションをつくる暮らしをしていますが、この記事ではブラウザで動作するグラフィックスAPIであるWebGPUについて紹介します。

WebGPUとは

WebGPUとは、W3Cが開発しているブラウザでGPUを使った処理を実行するためのAPIです。

既存の似たAPIとしてWebGLがありますが、WebGLOpenGL ESを元にしたAPIである一方、WebGPUはDirectX 12, Metal, VulkanのようなOpenGLよりも低レベルなグラフィックスAPIをモデルにしているので、WebGLよりもドライバー負荷が低く、よりハードウェアの性能を生かしたアプリケーションを実装しやすくなります。

WebGPUの実装は各ブラウザで進められていますが、仕様はまだEditor’s Draft*1な上、ブラウザごとに実装状況はまちまち*2で、さらに言うと、Safariではシェーダ言語としてWSL(Windows Subsystem for LinuxではなくWeb Shading Language)を利用するのですが、現時点のChrome, FirefoxではSPIR-Vという別の言語を利用するAPIとなっているなど、仕様レベルの差もそれなりにあります。

最終的なAPIが確定し、各ブラウザで安定して動作するまでにはまだまだ時間がかかりそうな様子ではありますが、ブラウザで低レベルグラフィックスAPIを触れるようになると、C/C++やOSに依存した低レベルグラフィックスAPIに親しんでいない開発者でもGPUの高度な機能を使えるようになるので、夢のある技術だなと思っています。

WebGPUに触れてみる

さて、WebGPUの雰囲気を説明するために、素朴なパーティクルを実装してみました。

f:id:maku693:20191204053019g:plain

実際に動作するデモはこちらです…が、今回はWSLを使ってみたかったので、Safari Technology Previewでしか動作しません。動かしてみたい方は、Safari Technology Previewをインストールした上でDevelop > Experimental Features > WebGPUにチェックをつけてご覧ください。





ここからはいくつかプログラム的な見どころをご紹介します。

JavaScriptではなくコンピュートシェーダでパーティクルの位置を計算している
struct Particle {
  float2 position;
  float2 velocity;
}

[numthreads(1, 1, 1)]
compute void ComputeMain(
  device Particle[] prevParticles : register(u0),
  device Particle[] nextParticles : register(u1),
  float3 threadID : SV_DispatchThreadID
) {
  uint index = uint(threadID.x);

  if (${particleCount} <= index) {
    return;
  }

  float2 position = prevParticles[index].position;
  float2 velocity = prevParticles[index].velocity;

  position += velocity;

  if (1.0 < position.x) {
    position.x = -1;
  }
  if (position.x < -1.0) {
    position.x = 1;
  }
  if (1.0 < position.y) {
    position.y = -1;
  }
  if (position.y < -1.0) {
    position.y = 1;
  }

  nextParticles[index].position = position;
  nextParticles[index].velocity = velocity;
}

シェーダというのはGPU上で実行されるプログラムのことで、コンピュートシェーダというのはその中でも汎用計算向けのシェーダです。今回のプログラムではcompute void ComputeMain() がそれで、WebGL 1.0で同じようなことをしたい場合、パーティクルのそれぞれの粒の位置データを一度テクスチャとして書き出すなどのワークアラウンドが必要だった*3のですが、WebGPUでは直接GPU上のメモリを読み書きできるようになったので、より素直にGPU上で実行したい処理を記述できるようになりました。

main 関数を async にしている
async function main() {
…(中略)…
}

DOMContentLoadedのコールバックに指定しているmain関数をasyncにしています。バッファ(GPUのメモリを表現するオブジェクト)を更新する処理など、WebGPUのいくつかのメソッドはPromiseを返すようになっており、いちいちPromiseをメソッドチェーンで処理しているとネストがかなり深くなってしまうので、awaitを使えるようにしています。
WebGPUがPromiseを使っているのは、GPUとの通信がキレイに現代的なJavaScriptAPIとしてモデリングされているように感じて好きなポイントです。

レンダリングコマンドとコンピュートコマンドを同時にGPUに送信している
    const commandEncoder = await device.createCommandEncoder();

    const computePassEncoder = commandEncoder.beginComputePass();
    computePassEncoder.setPipeline(computePipeline);
    computePassEncoder.setBindGroup(0, particleBindGroups[t % 2]);
    computePassEncoder.dispatch(particleCount, 1, 1);
    computePassEncoder.endPass();

    const renderPassEncoder = commandEncoder.beginRenderPass({
      colorAttachments: [
        {
          attachment: swapChain.getCurrentTexture().createDefaultView(),
          loadOp: "clear",
          clearColor: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          storeOp: "store"
        }
      ]
    });
    renderPassEncoder.setPipeline(renderPipeline);
    renderPassEncoder.setVertexBuffers(0, [particleBuffers[(t + 1) % 2]], [0]);
    renderPassEncoder.draw(particleCount, 1, 0, 0);
    renderPassEncoder.endPass();

    device.getQueue().submit([commandEncoder.finish()]);

WebGPUというより低レベルグラフィックスAPI全般に共通の特徴ですが、コマンドバッファ(GPUへの連続した命令を記録する仕組み)の管理がプログラマに任されており、かつ自由度も高いので、レンダリングコマンド(グラフィックス描画命令)とコンピュートコマンド(汎用計算命令)を同時にGPUに送信できます。
これの何が嬉しいかというと、この程度のデモではそこまでメリットがないのですが、例えばWebGLではコンピュートコマンドの実行終了を待ってからレンダリングコマンドを発行するしかなかったところ、WebGPUでは待つ必要がないので、CPU側の待ち時間を減らすことができ、より効果的にCPUを活用できます。

おわりに

この記事ではWebGPUについて紹介しました。少しでもグラフィックスプログラミングに興味を持ってもらえたら嬉しいです。明日はid:ma2sakaさんです!

*1:https://gpuweb.github.io/gpuweb/

*2:https://github.com/gpuweb/gpuweb/wiki/Implementation-Status

*3:実はWebGL2.0にはコンピュートパイプラインの仕様があるのですが、WindowsLinuxChromeFirefoxでしか実装されていません。

masawada Advent Calendar 2019 1日目: どこでもmasawada

この記事は masawada Advent Calendar 2019 の1日目の記事です。

こんばんは、id:masawada さんの同僚の id:maku693 です。いつもお世話になっております。

masawadaさんは最高なので、現実に隣にいない場合でも見守っていてほしくなる事がありますよね。
そんなみなさまのためにARKit / ARCore対応端末でいつでもどこでも好きな場所にARでmasawadaさんを召喚できるようにしました(読み込みにちょっと時間がかかります)。

もしPCでご覧の方はスマホで見てみてください。右下にARっぽいアイコンのボタンが表示されるはずです。

f:id:maku693:20191130232115p:plain
ARっぽいボタン

これを押してみるとなんと!あらゆる場所にmasawadaさんを呼び出せます。

f:id:maku693:20191130235908p:plain
デスクトップmasawadaさん

f:id:maku693:20191130232225p:plain
実物の2倍くらいあるmasawadaさん

これでいつでもmasawadaさんに見守ってもらえますね。

今回はmasawadaさんが提供してくれた3Dモデルをgltf (glb) とusdzに変換し、glbは Blender で、usdzはAppleusdz tools でglbから変換しました。
表示には <model-viewer> Web Component を使っています。ファイルは一旦自分のforkに置いてありますが、後ほどPull Requestを出そうと思います。

実はAndroidで動作確認してないので動かなかったら教えてください。

ではまた15日の記事でお会いしましょう。サンキューです。

大乾杯 開発振り返り

こんばんは、id:maku693です。

3/22の晩から3/23の晩にかけて、「大乾杯 THE GREAT KANPAI」という乾杯シミュレーターを開発しました。今回は「24時間でゲームを作るやつ」を始めてちょうど10本目で、今まで開発してきたゲーム達の集大成的な、クォリティの高いものができあがって満足しています。

完成してから3週間ほど経ってしまいましたが、今回も「よかったこと」「よくなかったこと」という観点で振り返りをしておこうと思います。

@noir_neoも記事を書いているので、合わせてお楽しみください。

noir-neo.hatenablog.com

続きを読む

POPPO SHOOTER 2019 開発振り返り

2018/1/11の夜から24時間、私ふくめ3人のチームでPOPPO SHOOTER 2019というPC向けブラウザゲームをつくりました。

節分にちなんで(?)、鳩が豆鉄砲を打ちながら鬼を倒すというゲームです。遊び方はゲーム画面右下の「?」ボタンで確認できます。

いつも開発を始める前に全員で30分程度前回の振り返りをするのですが、1カ月以上間が空くことも多く、あまり当時のことを覚えておらず毎回ちゃんと振り返れていないので、宣伝を兼ねた備忘録として

  • よかったこと
  • よくなかったこと

という観点でこの記事にまとめておきます。

続きを読む