こんにちは、エンジニアのオオバです。
Unityのシェーダーはキーワードを追加して処理を分岐させて開発します。シェーダーを書いていくと、知らず知らずのうちにシェーダーバリアントが大量に増えることがあるのです。
シェーダーバリアントとはシェーダーの分岐処理によって発生するバリエーションのこと。
シェーダーバリアント自体は特に問題ではありません。問題なのは開発者が意図しないバリアントが作られ想定使用メモリを超えていた場合です。
そこで本記事ではシェーダーが占有するメモリ使用量を計測する方法を紹介していきます。シェーダーは思っている以上にメモリを占有していることが多いです。
特にメモリがギリギリの製品を作っているときには重要なチューニング要素になるため、最後まで読んでみてください。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
シェーダーバリアントとは?
そもそもシェーダーバリアントとは何か解説します。シェーダーバリアントはシェーダーの分岐処理で発生したバリエーションシェーダーです。
例えば次のシェーダーコードの一部を確認してみましょう。
fixed4 frag (v2f i) : SV_Target
{
// デフォルト色(緑)
fixed4 color = fixed4(0, 1, 0, 1);
#ifdef YELLOW
color = fixed4(1, 1, 0, 1);
#elif defined(BLUE)
color = fixed4(0, 0, 1, 1);
#endif
return color;
}
「マクロ」を使って分岐したシェーダーです。YELLOWマクロが有効なら黄色で描画、BLUEなら青といった感じです。
上記のコードはビルド時にコンパイルされ、YELLOW用、BLUE用のシェーダーを別々で作成します。つまり1つのシェーダーファイル内でif文のように切り替わるのではなく、マクロの数だけ分岐したバリアントが作成されるのです。
シェーダーバリアントを作成する方法
ただ単に #ifdef~#endif
で分岐させればマクロが作られるわけではありません。シェーダーバリアントを作成するための指示が必要です。それがmulti_compile
と shader_feature
です。
#pragma shader_feature _ YELLOW
上記のように「shader_feature(またはmulti_compile)」でマクロを指定することで必要な分岐が作られます。
multi_compile
、 shader_feature
については詳しくはこちらの記事で解説していますのでぜひ読んでみてください。
シェーダーが占有するメモリって何?
シェーダーは描画タイミングになったタイミングでGPUに転送され、VRAMにロードされます。VRAMとはVideo Runtime Access Memoryの略で、GPU内のメモリ(以下:VRAM)です。
VRAMにシェーダーは展開しキャッシュされます。今回知りたい「シェーダーの使用メモリ量」は、CPU側のメモリ(RAM)ではなくGPU側のVRAMです。
なぜならRAM内にロードされたシェーダーは、
- シェーダーのバイナリデータ
- バリアント管理情報
といったそこまで問題にならない要素だからです。使用メモリ量も少なく無視して良いと考えています。
重要なのはVRAM側の使用量です。シェーダー内で記述したテクスチャバッファやフレームバッファはVRAM内で確保されるため、多くのメモリを使用しがち。
シェーダーの使用メモリ(VRAM)を知っておくことは非常に重要なのです。
いつの間にかシェーダーの使用メモリ量が増える理由
シェーダーバリアントがどのくらい発生するかは、実際のシェーダーを開発し運用してみないと分かりません。
というのも shader_feature
で設定したマクロの分岐は実際に使用される数しか作成されないからです。未使用の分岐はストリップ(除外)されます。
つまり開発当初はあまり作成されなかったバリアントが、 仕様変更やブラッシュアップによって分岐が増え、バリアントのが数が増えてしまった ということはよくあります。
だから シェーダーバリアントがどのくらい発生するかはシェーダー開発時では把握しづらい のです。
シェーダーの最大使用メモリ量の計測方法
シェーダーバリアントが大量に発生したとしても、 シェーダーが使用する最大量を把握 しておけば問題にならないこともあります。最大使用量使ったとしても許容範囲なら問題になりませんよね。また最大使用量を知っておくことで開発中のピークメモリ使用量の目安も分かります。
シェーダーの最大使用メモリ量は知ってて損はありません。そこでシェーダーの最大使用メモリ量の計測方法を紹介します。
手順はシンプルで、一時的に shader_feature
を multi_compile
にするだけ。
#pragma shader_feature _ HOGE FOO
↓
#pragma multi_compile _ HOGE FOO
上記のようにshader_featureをmulti_compileに置き換えるだけです。
multi_compile
は使用未使用問わず全てのシェーダーバリアントを作成します。つまりそのシェーダーが使用する最大メモリ量の状態になるということです。
この状態でメモリを計測してすることでシェーダーの最大メモリ使用量がわかるのです。
シェーダーのメモリ使用量の計測方法
実際にシェーダーが使用するメモリ量を計測していきます。計測方法は端末にアプリをインストールしてUnityが提供するMemory Profilerを使用していきます。
Unityエディタで計測するとノイズが入ってしまい、ただしい数値が取得できないためオススメしません。数値が上下する傾向を調べるくらいなら良いですが、しっかり調査する場合は端末で計測することをオススメします。
今回は手持ちのPixel7Proを使用します。
Memory Profilerを使って計測
今回はMemory Profilerを使って計測していきます。
メニューWindow -> Analysis -> Memory Profiler
から Memory Profiler を開きます。
Memory Profilerを開いたら、①のプルダウンから端末を選択肢ます。次に②Captureボタンをクリックします。
するとプロファイルした内容が表示されます。
③Graphics(Estimated) を確認しましょう。「Graphics(Estimated)」がVRAMにロードされたメモリ量ということです。
今回の例だと58.1MB使用していることが分かります。開発中にこの数値はどんどん大きくなっていくため、定期的にチェックして想定内の数値化確認しながら進めていきましょう。
シェーダーの使用メモリ量の確認方法まとめ
本記事ではUnityで使用するシェーダーがどのくらいメモリを使用するのか確認する方法を紹介してきました。
記事の内容を簡単にまとめます。
①マクロによってシェーダーバリアントが作られる
②シェーダーはRAMではなくVRAMにロードされる
③shader_featureをmulti_compileに一時的に変更して最大量を計測
④Unity純正MemoryProfilerでメモリ使用量の確認
multi_compile
、 shader_feature
で作成できるシェーダーバリアント機能はとても便利です。フルスクラッチで全パターン分シェーダーを書いていたら大変です。便利な機能の恩恵はしっかり受けていきましょう。
しかし神機能も調子にのって使いすぎるとシェーダーバリアントが大量に作られてしまい、メモリを大量に使用してしまいます。
最大でどのくらいメモリを消費するかを把握し、アプリ全体のピークメモリを制御する事で安全に開発を進められます。
MemoryProfilerの値をチェックして異常な値になっていないかをウォッチしていきましょう。

筆者のXをフォローしよう
- Unity6000.0.32f1
- Pixel7Pro