Article by: (読了時間:6分)
よくある話です。クラッシュレポートがキューに届きます。スタックトレースはきれいに取れているものの、指しているのはいかにも無害そうな関数だけ。プレイヤーがその直前に何をしていたのかを示す手がかりは、どこにもありません。ボス戦の真っ最中だったのか、ロード画面で Alt+Tab を押していたのか、チュートリアルで立ち止まっていただけなのか、QAでは再現できず、プレイヤーからのバグ報告は「ゲーム落ちたw」の一言。残されたコアダンプとコールスタックを前に、当てものクイズを始めるしかありません。
スタックトレースは、コードがどこでクラッシュしたかは教えてくれます。しかし、なぜクラッシュしたのかまでは教えてくれません。ゲームは、一般的なデスクトップアプリケーションやモバイルアプリケーションに比べて、実行環境による影響をはるかに受けやすいソフトウェアです。だからこそ、「なぜ」の答えは、たいていクラッシュ直前の数秒間にあります。
Session Replayが埋めてくれるのは、まさにそのギャップです。新たに Session Replay に対応したことで、Unreal Engine 向けの Sentry SDK は直近数秒のゲームプレイを継続的に録画し、そのクリップをクラッシュレポートに添付できるようになりました。クラッシュが Sentry に届くと、その映像も一緒に届きます。おかげで物事がおかしくなった瞬間まで、プレイヤーが見ていたものをそのまま見られるのです。
Session Replay で得られるもの
リプレイ付きのクラッシュが届くと、クラッシュイベントにひも付いた形で Replays ページに表示されます。そこでは、ゲームの実際のレンダリング済みフレームを再生できるほか、その過程で発生した重要な出来事を時系列に並べた、スクラブ可能なタイムラインも確認できます。
▶ インタラクティブデモ:https://demo.arcade.software/8AgdLvo1bz99viH3a842/?embed
デバッグの主役は、プレイヤーの隣に並ぶパネルです。映像と AI によるリプレイ要約に加えて、各リプレイでは次の情報も確認できます。
- タグとコンテキスト — OSとそのバージョン、リリース、クラッシュの種類、リージョンなど、本来ならプレイヤーに確認しなければ分からない情報です。
- Breadcrumbs — エラーやゲームプレイ中のイベント、独自に記録したカスタム breadcrumbs が映像と同期したタイムライン上に表示されます。
- ログ — ゲームのログ出力を映像に合わせてタイムスタンプ付きで確認できます。
- トレース — クラッシュに至るまでのスパンが共通のトレースIDで結び付けられています。バックエンドが関係するクラッシュであれば、同じトレースに含まれるサーバー側のスパンもここからたどることができます。
上の例では、これだけで何が起きたのかが分かります。開始から3秒後、チェックアウトのHTTPリクエストが不正な値を返し、ensure が発火。プレイヤーはレベル2に到達し、「Wrath」の攻撃モディファイアを選択。そして00:22、ASentryTowerTurret::Shoot で致命的なクラッシュが発生します。
これは、クラッシュそのものが再現手順を教えてくれるようなものです。しかも、映像を確認するためにクラッシュの詳細画面を離れる必要はありません。リプレイはクラッシュイベントのページにそのまま埋め込まれているため、Issueのトリアージは直前数秒のゲームプレイを確認するところから始められます。さらに詳しく調べたくなったら、ワンクリックでリプレイの全画面表示へ切り替えられます。
▶ インタラクティブデモ:https://demo.arcade.software/wbJ8cKhmNjSzcdAECCz1/?embed
仕組み
Unreal SDK でのリプレイ取得の初期の試みは、プラットフォームベンダーの機能(たとえば Xbox の GDK の game DVR)に頼っていました。それでも動きはしますが、プラットフォームごとに挙動が違ううえ、そもそもリリースできないものもあります。
今回の新しいデスクトップ/モバイル向けの実装では、逆のアプローチを取りました。録画パイプラインを Unreal ネイティブの機能だけで組み立て、エンジンが動く場所ならどこでも同じコードが走るようにしたのです。主役は3つあります。
バックバッファのキャプチャ はレンダースレッド上で Slate の OnBackBufferReadyToPresent をフックし、設定されたキャプチャフレームレート(デフォルトは 30 FPS。ゲームがそれより速く描画できる場合はフレームを間引きます)に絞り込みつつ、各フレームをエンコーダーが扱えるテクスチャへコピーします。バックバッファには多くのフォーマット(10 ビットや HDR など)があり、Slate はそれらをシェーダーリソースとして生成しません。そのためキャプチャの経路ではスクラッチテクスチャへハードウェアコピーを行い、フォーマット変換はエンジンのピクセルシェーダーに任せます。すでに BGRA8 で描画している場合は、単純な GPU コピーへと簡略化されます。
ビデオエンコーダー は専用のスレッドで動き、キャプチャしたフレームを Unreal の AVCodecs プラグイン群(NVIDIA なら NVENC、AMD なら AMF、Apple なら VideoToolbox)を介してハードウェアの H.264 エンコーダーへ送ります。適切なエンコーダーはプレイヤーの GPU に応じて実行時に選ばれます。エンコード結果は、キーフレームの区切りで短いフラグメントにまとめられ、フラグメント化 MP4 として多重化されます。
リプレイレコーダー はその全体を取りまとめます。完成したフラグメントをリングバッファで回しながら保持し、常に設定されたリプレイ時間分だけを残します。そして定期的にディスク上の完全な、再生可能な MP4 へと組み立てます。各スナップショットは一時ファイルに書き出され、アトミックにリネームされるため、書きかけで壊れた映像が残ってしまうことはありません。
クラッシュが発生すると、Unreal SDK は最新のクリップをクラッシュレポートに関連付け、Sentry Envelope にまとめて、自動的に Sentry へアップロードします。

プレイヤーのマシンに対応するハードウェアエンコーダーがない場合、この機能はそのセッションのあいだ自動で無効になり、警告をログに残します。そのためクラッシュレポートそのものが影響を受けることはありません。
実行時のコストは?
「ゲームの映像を継続的にエンコードする」と聞くと、いかにもゲームのフレーム予算をぺろりと平らげそうな話に思えます。そこで、実際に計測してみました。
エンジンの CSV プロファイラーを使って、Unreal のデモゲームをプロファイルしました。ベースラインとしてリプレイをオフにした状態で1回、オンにした状態でもう1回です。あえて 4K フルスクリーンで実行しています。バックバッファが大きいほどコピーとエンコードのコストも大きくなるため、4K が最悪ケースの目安になり、より低い解像度でのオーバーヘッドは下がる一方だからです。
| 指標 | ベースライン | リプレイ ON | Δ | Δ % |
|---|---|---|---|---|
| フレームタイム | 5.56 ms | 5.77 ms | +0.20 ms | +3.6% |
| レンダースレッド | 5.56 ms | 5.67 ms | +0.11 ms | +1.9% |
| ゲームスレッド | 1.72 ms | 1.82 ms | +0.10 ms | +5.8% |
| GPU | 4.96 ms | 5.04 ms | +0.08 ms | +1.6% |
| RHI スレッド | 1.81 ms | 1.82 ms | +0.01 ms | +0.5% |
結論は4K で1フレームあたり約 0.20 msです。これは、60 FPS での 16.7 ms のフレーム予算の約1.2%、120 FPS なら約2.4%にあたります。その大半はレンダースレッドのバックバッファフックによるもので、エンコードやファイルのローテーションはバックグラウンドスレッドで行われます。そのため、ゲームスレッド、RHI スレッド、GPU は、ほとんど気づきもしません。
はじめかた
Session Replay は Sentry Unreal SDK 1.16.0 に同梱されています。プラグインをすでに設定済みなら、トグル1つで使い始められます。
- エンジンプラグインの
AVCodecsCoreに加えて、カバーしたい GPU ベンダーごとのコーデックプラグイン(Windows と Linux ではNVCodecsやAMFCodecs、Apple のプラットフォームではVTCodecs)を有効にする。適切なものは実行時に選ばれるため、すべてを併存させておいて問題ありません。 - Project Settings > Plugins > Sentry で「Session Replay」を展開し、「Enable session replay (experimental)」をオンにする。
- プラグインをリビルドする。リプレイをコンパイルに含めるかどうかはビルド時に決まります。そのため設定を切り替えたら、プロジェクトの一時ファイルを削除してクリーンな状態から始め、リビルドしてください。
これで完了です。リプレイの長さ、キャプチャフレームレート、ビットレート、フラグメントの長さはいずれも調整可能です。(すべての設定項目とプラットフォームごとの要件は Session Replay のドキュメントで確認できます。)
1つ、重要な注意点があります。現時点では自動マスキングはありません。録画はレンダリング済みのフレームをそのまま取り込むため、プレイヤー名やチャット、画面上のそのほか何もかもが映像に残ります。ゲームが描画する内容が録画しても問題ないものであること、そしてプライバシーポリシーがそれを踏まえていることを必ず確認してください。
一緒に育ててください
Unreal Engine 向けの Session Replay は実験的な機能であり、私たちが次に何を作るかは、寄せられるフィードバックに大きく左右されます。
というわけで、ぜひプロジェクトで試し、映像つきのクラッシュをいくつか眺めてみて、足りないものを教えてください。GitHub で Issue を立てる(あるいは既存のものにリアクションするだけでも大丈夫です)か、Discord で気軽に話しかけてください。「再現できません」で片づくクラッシュが、みんなにとって少しでも減りますように。
Original Page: Session Replay for Unreal Engine: see the crash before the crash
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談は「お問い合わせ」からお気軽にお問い合わせください。

