今回は Unity でビルドした iOS アプリ上から Metal Debugger のキャプチャを行う方法について解説します。
記事の要約
ある程度知っている人向けにタイトルに対する要約だけ先に書いておくと、Unity の FrameCapture API を呼び出すだけでアプリ上からキャプチャ可能です。
Metal Debugger について
Metal Debugger は Xcode に搭載されている Metal 向けのデバッグツールであり、キャプチャしたフレームを元にレンダリングパイプラインの詳細を探り、描画コマンドの調査やパフォーマンスの分析などを行うことができます。
ツール自体はネイティブアプリケーション向けのものとなりますが、Unity 製アプリでも活用することが出来るので、使いこなせば詳細な分析などに役立てられるかと思います。
具体的な使い方については本題から逸れるので記事中では解説しませんが、いくつか Unity 向けに関する参考記事があるので、リンクの方を載せておきます。
キャプチャの手順
キャプチャするには Xcode に接続した状態で以下の UI からトリガーする方法がよく知られているかもしれませんが、これ以外にもコード上から API を呼び出すことでトリガーする方法もサポートされてます。

その上で、Unity には内部的にこちらの API をラップしていると思われる FrameCaputire API が存在しており、こちらをアプリの開発機能として組み込めば、アプリ内からキャプチャしてそのまま Metal Debugger を立ち上げたり、キャプチャしたデータを端末のストレージに保持して後から PC に転送して分析すると言ったことが出来るようになります。
Unity からの呼び出し手順について
ここからは幾つかの Tips を交えつつ、呼び出すまでの手順について解説していきます。
また、サンプルプロジェクトも合わせて用意しているので、気になる方はこちらもご覧ください。
フレームキャプチャオプションの有効化
前提としてフレームキャプチャを行うには、Apple 公式ドキュメントの「Configure the GPU Frame Capture options」にもある通り、スキームを編集してフレームキャプチャオプションを有効にする必要があります。
こちらはビルド後の Xcode プロジェクトを開いて手動で設定することも可能ですが、Unity にはビルド時にスキーム設定を変更するための API が用意されているので、PostProcessBuild
辺りで組み込めば設定の自動化が可能です。
(参考: 【Unity】iOSネイティブプラグイン開発を完全に理解する - Xcodeの設定を自動化する)
コードからの呼び出しの有効化
そしてあともう一つ、Xcode に未接続の状態でコード上から呼び出すには Info.plist
に MetalCaptureEnabled
と言う項目を ture
で追加する必要があります。1
こちらも PlistDocument 経由でビルド時に設定を自動化することが可能です。
注意点として、上記公式ドキュメントにもある通り、こちらを有効にするとパフォーマンス (CPU負荷) にも若干の影響が出るみたいです。
なので、基本的には開発ビルドに限定したり、更にこだわるなら GPU デバッグ用のビルドに限定して有効化すると良いかもしれません。
サンプルコード
これらを纏めたサンプルコードとしては以下のようになります。
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
namespace Example.Editor
{
internal static class XcodePostProcess
{
[PostProcessBuild]
private static void OnPostProcessBuild(BuildTarget target, string xcodeprojPath)
{
if (target != BuildTarget.iOS) return;
EnableMetalFrameCapture(xcodeprojPath);
}
private static void EnableMetalFrameCapture(in string xcodeprojPath)
{
// Scheme 側の有効化
// ref: https://docs.unity3d.com/ScriptReference/iOS.Xcode.XcScheme.html
{
var schemePath = $"{xcodeprojPath}/Unity-iPhone.xcodeproj/xcshareddata/xcschemes/Unity-iPhone.xcscheme";
var xcScheme = new XcScheme();
xcScheme.ReadFromFile(schemePath);
xcScheme.SetFrameCaptureModeOnRun(XcScheme.FrameCaptureMode.Metal);
xcScheme.SetDebugExecutable(true);
xcScheme.WriteToFile(schemePath);
}
// Info.plist 側の有効化
// ref: http://xcodedeveloper.apple.com/documentation/xcode/capturing-a-metal-workload-programmatically
{
var plistPath = Path.Combine(xcodeprojPath, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
var root = plist.root;
root.SetBoolean("MetalCaptureEnabled", true);
plist.WriteToFile(plistPath);
}
}
}
}
キャプチャ後に自動で Metal Debugger を立ち上げる
こちらは FrameCapture API
にある以下のメソッドを呼び出すだけであり、キャプチャ終了後に自動で Xcode の Metal Debugger が立ち上がります。
API | 概要 |
---|---|
BeginCaptureToXcode | キャプチャ開始 (終了するには EndCapture を呼ぶ必要あり) |
EndCapture | キャプチャ終了 |
CaptureNextFrameToXcode | 次の 1フレームだけキャプチャ (こちらは EndCapture を呼ぶ必要は無し) |
こちらを呼び出す際には Xcode にデバッグ接続している必要があります。
private void CaptureToXcodeExample()
{
// キャプチャを開始し、キャプチャ終了後に Xcode の Metal Debugger を立ち上げる
// NOTE: 終了させる際には次の `EndCapture()` を呼び出すこと
beginCaptureToXcode.onClick.AddListener(() =>
{
FrameCapture.BeginCaptureToXcode();
});
// キャプチャ終了
endCapture.onClick.AddListener(() =>
{
FrameCapture.EndCapture();
});
// 次の 1フレームだけキャプチャ (こちらは `EndCapture` を呼ぶ必要は無し)
captureNextFrameToXcode.onClick.AddListener(() =>
{
FrameCapture.CaptureNextFrameToXcode();
});
}
使い分けとしては、任意の範囲をキャプチャし続けたい場合には BeginCaptureToXcode
〜 EndCapture
を呼び、とりあえず1フレームだけキャプチャしたい場合には CaptureNextFrameToXcode
を呼ぶと言ったケースが考えられそうです。
キャプチャしたデータをトレースファイルとして保存する
次はキャプチャしたデータをトレースファイルとして保存する方法について解説します。
先ほど解説した方法は終了後に Metal Debugger を立ち上げることから、実行時には Xcode にデバッグ接続している必要がありましたが、こちらはアプリ側だけで完結するので Xcode に未接続の状態でも動作すると言った利点があります。
API の違いとしてもほぼ前述のものと変わっておらず、違いとしては書き込み先のファイルパスを指定する必要があるぐらいです。
(キャプチャデータの拡張子は .gputrace
)
API | 概要 |
---|---|
BeginCaptureToFile | キャプチャ開始 (終了するには EndCapture を呼ぶ必要あり) |
EndCapture | キャプチャ終了 |
CaptureNextFrameToFile | 次の 1フレームだけキャプチャ (こちらは EndCapture を呼ぶ必要は無し) |
private string _latestFilePath;
private void CaptureToFileExample()
{
// キャプチャを開始し、キャプチャ終了後にトレースデータを指定したファイルに書き込む。
// NOTE: 終了させる際には次の `EndCapture()` を呼び出すこと
beginCaptureToFile.onClick.AddListener(() =>
{
_latestFilePath = GetFilePath();
FrameCapture.BeginCaptureToFile(_latestFilePath);
});
// キャプチャ終了
endCapture.onClick.AddListener(() =>
{
FrameCapture.EndCapture();
});
// 次の 1フレームだけキャプチャ (こちらは `EndCapture` を呼ぶ必要は無し)
captureNextFrameToFile.onClick.AddListener(() =>
{
_latestFilePath = GetFilePath();
FrameCapture.CaptureNextFrameToFile(_latestFilePath);
});
}
private static string GetFilePath()
{
// NOTE: キャプチャファイルの拡張子は `.gputrace`
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var fileName = $"GpuCapture_{timestamp}.gputrace";
var filePath = Path.Combine(Application.persistentDataPath, fileName);
return filePath;
}
トレースデータは AirDrop 経由で macOS に渡すことも可能
こちらの API を用いた応用例として一つ挙げると、トレースデータは AirDrop 経由で macOS に渡すことも可能なので、例えばキャプチャ終了時にネイティブのシェア機能にファイルパスを渡すことで、以下のように簡単にトレースデータの受け渡しが行えたりします。
サンプルにはこちらの機能まで組み込んでいるので、よろしければ参考にしてみてください。2

(ただし、トレースデータはサイズが大きいかもしれないので注意...今回用意した簡易的なサンプルだけでも 338.2MB ありました。。)
トレースデータの開き方
トレースデータは macOS 上でそのまま開くことが可能であり、その際には以下のようなダイアログが表示されるので、キャプチャをトリガーしたデバイスを選択して「Replay」を押すことで Metal Debugger が立ち上がります。
(画像は公式ドキュメントより引用)
ちなみに、端末が複数接続されている場合には、その中からリプレイする端末を選択することが可能ですが、上記の公式ドキュメントにも書かれている通り、互換性や再現性の観点から基本的にはトリガーしたデバイスを選択しておくのが無難かと思われます。
サポートされているかどうかチェックする
また、これらの API がサポートされているかどうかは FrameCapture.IsDestinationSupported
からチェック可能です。
例えば Xcode にデバッグ接続していない状態で実行したり、前述した コードからの呼び出しの有効化 にて Info.plist
に MetalCaptureEnabled = true
を設定しない状態で実行すると false
が返ってくるみたいでした。
// サポート状況を表示
// DevTools → キャプチャしたら Xcode の Metal Debugger を立ち上げる
// GPUTraceDocument → キャプチャしたトレースファイルを保存する
var isDevTool = FrameCapture.IsDestinationSupported(FrameCaptureDestination.DevTools);
var isGpuTrace = FrameCapture.IsDestinationSupported(FrameCaptureDestination.GPUTraceDocument);
isSupportedText.text = $"[Supported]: DevTools: {isDevTool}, GPUTrace: {isGpuTrace}";
Debug.Log(isSupportedText.text);
おわりに
今回は Unity の FrameCapture API
について簡単に解説させて頂きました。
ちなみに、少し余談話をすると、この記事を書き始める前までは FrameCapture API
の存在を把握しておらず、その時までは Low-Level Native Plugin Interface を用いて Unity の Metal Command Buffer をフックし、ネイティブ API を呼び出すと言った力技で何とかしようとしていました。。3
そんな中で「もっと楽に出来ないかな?」と思い、適当に Deep Research を回して調べるなどしていたら↓のドキュメントに書かれていることに気付き、備忘録として今回の記事を書こうと思ったのが執筆経緯だったりします。。
とは言え、アプリ上から Xcode の接続なしにキャプチャを行えると言うのは心強く、今後はもっと応用例なども探っていければなと思います。
参考リンク
- Apple 公式ドキュメント
- Unity 公式ドキュメント
- 参考記事