CORETECH ENGINEER BLOG

株式会社サイバーエージェント SGEコア技術本部 技術ブログ

Instant Replay for Unity が WebGL / WebGPU での画面録画に対応しました

こんにちは、コア技術本部の瀬戸です。

私たちが OSS として公開している Instant Replay for Unity は、Unity で実行中のゲーム画面を常時録画し、任意のタイミングで直近 N 秒を動画として書き出すことができるライブラリです。

過去の Instant Replay for Unity 関連記事

先日リリースした v1.5.0 では、新たに WebGL / WebGPU プラットフォームに対応しました。Instant Replay for Unity は、特に Web においては手軽にリプレイ動画を出力して共有できる機能としてお使いいただけるのではないかと思っています。詳しい導入方法等は上記の記事やREADMEを参照していただけると幸いです。

この記事では WebGL / WebGPU 対応の裏側について解説します。

プラットフォームのサポート状況

Instant Replay for Unity はこれまで iOS / Android / macOS / Windows / Linux の 5 つのプラットフォームをサポートしてきました。プラットフォームごとに使用できるエンコーダー API は異なっており、それぞれのプラットフォームに固有の実装を行っています。

プラットフォーム エンコーダー API
iOS / macOS Video Toolbox (H.264), Audio Toolbox (AAC)
Android MediaCodec (H.264 / AAC)
Windows Media Foundation (H.264 / AAC)
Linux システムにインストールされた FFmpeg (H.264 / AAC)

また、これらの API の呼び出しは全て Rust によるネイティブプラグイン実装を経由して行われており、C# 側のコードはプラットフォームに依存しない形で実装されています。

今回は、ここに新しく Web サポートを追加しました。これには Rust の Web 向けビルドや、Web 上での動画エンコードなど、いくつかの技術的なチャレンジがありました。

Web におけるネイティブプラグイン

Unity の Web ビルドでは、ユーザースクリプト (C#) を IL2CPP で C++ に変換した後、エンジンコード (C++) とリンクする形で WebAssembly にコンパイルします。C++ から WebAssembly へのコンパイルには Emscripten が使用されています。実は、C++ や Rust で書かれたコードを事前に Emscripten を使用して WebAssembly の静的ライブラリ形式としてビルドしておくことで、Unity にネイティブプラグインとして組み込むことが可能です。

Rust では wasm32-unknown-emscripten ターゲットを指定してビルドすることで Emscripten 向けの WebAssembly を生成できます。

注意点として、Emscripten はコンパイラのバージョンやコンパイラオプション等によって ABI の互換性が失われる可能性があります。厳密にはネイティブプラグインをビルドする際の Emscripten (LLVM) のバージョンやコンパイラオプションを Unity が Web ビルド向けに使用しているものと一致させるべきですが、Rust はコンパイラ自身に LLVM を組み込んでいますし、libc に関しても特定の Emscripten バージョンの利用を前提として実装されているため、厳密な ABI 互換性の保証は難易度が高いです。とはいえ、最新の Rust で wasm32-unknown-emscripten ターゲットを使用してビルドした WebAssembly でほとんど問題なく動作したため、今回はそれでよしとしています。

一点だけ問題となったのは、Rust はデフォルトでいくつかの WebAssembly 提案を有効化しますが、その中の一部が Unity の Web ビルドと互換性がありませんでした。これらの提案を無効化するために RUSTFLAGS=-Ctarget-cpu=mvp を使用してビルドを行っています。また、これに伴い Rust の標準ライブラリ等もビルド済みのものではなく独自にビルドする必要があるため、build-std を使用しました。以下はビルドコマンドの例です。

RUSTFLAGS=-Ctarget-cpu=mvp cargo +nightly build -Z build-std=panic_abort,std --target wasm32-unknown-emscripten

WebCodecs API

WebCodecs API は、Web ブラウザ上でビデオやオーディオのエンコード / デコードを行うための比較的新しい API です。ハードウェアエンコーダーをサポートするブラウザであれば、JavaScript や WebAssembly でソフトウェアエンコーダーを実装するよりも遥かに効率的に動画をエンコードできます。

2025年には Safari での WebCodecs 対応が拡充され、ほとんどの主要なブラウザで音声と映像のエンコーダーが利用可能になりました。今回、Instant Replay for Unity の WebGL / WebGPU 実装では、この WebCodecs API を利用してエンコード処理を行っています。

Emscripten でビルドされる Rust コードから JavaScript の API を呼び出すには、Emscripten が提供する API が利用できます。今回は emscripten_run_script() を利用して、WebCodecs API を呼び出す JavaScript コードを実行しています。また、この JavaScript コードは元々 TypeScript で実装されており、Rust のビルドスクリプトの中で JavaScript にトランスパイルされ、Rust のソースに埋め込まれるようにしています。

非同期対応

Unity の Web ビルドは基本的にシングルスレッドで実行されます。元々 Rust 実装は async / await のために tokio のマルチスレッドランタイムを使用していましたが、これは Web で動作しません。そこで、PlayerLoop の中でタスクを実行するシングルスレッドランタイムを導入しました。

📝 Unity 6 で Web ビルドでの C/C++ マルチスレッド を有効化できるようになりましたが、Rust の wasm32-unknown-emscripten での対応状況がわからなかったため、使用しませんでした。マルチスレッド化すれば全体的なスループットの向上が見込めるものの、最も高負荷である動画のエンコード処理については WebCodecs API を経由してメインスレッドをブロックせずに実行されるため、マルチスレッド化の恩恵は限定的だろうと考えています。

書き出し

Web 以外のプラットフォームでは、エンコードされた動画ファイルはローカルのファイルシステムに書き出されますが、Web では基本的にファイルシステムに直接アクセスできません。Origin Private File System を利用する方法もありますが、一度ローカルに作成したファイルをアプリ側が管理する必要があるため、いちライブラリの機能としてはややヘビーです。そこで、よりシンプルな方法として、動画ファイルをブラウザのメモリ上に Blob として保持し、URL.createObjectURL() を使用してダウンロードリンクを生成する方法を採用しました。これによりユーザーは録画された動画を簡単に「ダウンロード」して共有できるようになります。この方法ではディスクへの書き出しをストリーミングできないためメモリプレッシャが高くなってしまいますが、Web 上での手軽な動画共有のためには十分な方法だと考えています。

おわりに

Instant Replay for Unity は引き続き OSS として開発中です。お気づきの点があれば Issue や PR をお寄せいただけますと幸いです。