【Unity/Shader】Unity2022.1から追加された dynamic branch を検証

お久しぶりです。

前回の記事からもう2年も経ちましたか…

仕事もプライベートもバタバタしてめちゃくちゃサボってますね(;・∀・)

 

今回は Unity2022.1 から追加された dynamic branch というものを検証してみました。

これを書いている時点では dynamic_branch で検索しても、まだ公式のマニュアルしかヒットしなかったので、

それをまとめていこうと思います。

公式ドキュメントを確認

 まず、こちらの Unity 2022.1.0 のリリースノートを見てみます。

 

What's new in Unity 2022.1.0 - Unity
https://unity3d.com/jp/unity/whats-new/2022.1.0

Features の Shaders の部分に #pragma dynamic_branch#pragma dynamic_branch_local が追加されたと書かれています。

 

次は 2022.1 のドキュメントの Shader 部分を見てみます。

 

Unity - Manual: Declaring and using shader keywords in HLSL
https://docs.unity3d.com/2022.1/Documentation/Manual/SL-MultipleProgramVariants.html

 

 

このように今までの shader_feature と multi_compile に加えて dynamic_branch が加わっています。

さて、この dynamic_branch は今までの2つと何が違うのか。

上記画像の右部分にも書かれていますが、バリアントを作らずに動的に分岐が行われるようです。

 

また、同じドキュメントページの下の方にこんな記述もあります。

こちらをまとめると、キーワードでの分岐には if や #if や #ifdef の3種類で書くことができますが、

#if や #ifdef ではなく if (キーワード) {} という書き方をしておくことで、

dynamic_branch に変更してバリアント数を減らしたいとなった場合に分岐部分は修正せずに

 

#pragma multi_compile QUALITY_LOW QUALITY_MED QUALITY_HIGH

      ↓

#pragma dynamic_branch QUALITY_LOW QUALITY_MED QUALITY_HIGH

 

のように shader_feature / multi_compile を dynamic_branch に書き換えるだけで

切り替えが可能になるとのことです。

 

なので、普段自分は #if や #ifdef で書いていましたが、今回検証に使った Shader はこの書き方に則って

分岐部分は if で書き #pragma キーワードディレクティブ部分が異なるようなファイルで検証しています。


検証環境

Unity のバージョン : Unity 2022.1.18f1  ( + memoryprofiler 0.7.1-preview.1 )

使用したグラフィックス デバッガー : RenderDoc v1.22

使用した端末 : Xperia XZ Premium (2017年の端末)

 

今回のテスト用 Unity プロジェクトはこちらです。

https://github.com/redglasses67/DynamicBranchTest

 

検証に使用したShaderの内容

● KeywordEnum でキーワード定義

 ① MultiCompileShaderSimple.shader

 ② MultiCompileShaderComplex.shader

 ③ DynamicBranchShaderSimple.shader

 ④ DynamicBranchShaderComplex.shader

 ⑤ IfOnlyShaderSimple.shader

 ⑥ IfOnlyShaderComplex.shader

 

・MultiCompile~ で始まる①②は #pragma multi_compile で定義

・DynamicBranch~ で始まる③④は #pragma dynamic_branch で定義

・IfOnly~ で始まる⑤⑥は #pragma での定義なし

という違いがあります。

 

下記に表示しているのが ② の MultiCompileShaderComplex.shader のコードで、

この20行目が③④の場合は #pragma dynamic_branch だったり、⑤⑥の場合はそこがない状態になっています。

 

また、

・~ShaderSimple.shader と付いている①③⑤は単純にテクスチャーだけをサンプリングして描画

・~ShaderComplex.shader と付いている②④⑥は負荷が高いと言われる超越関数 (pow, exp, log, cos, sin, tan)を意味もなく詰め込んで計算して描画

という違いがあります。

 

① MultiCompileShaderSimple.shader

④ DynamicBranchShaderComplex.shader

●Toggle でキーワードを定義

 ⑦ MultiCompileShaderSimple_Toggle.shader

 ⑧ MultiCompileShaderComplex_Toggle.shader

 ⑨ DynamicBranchShaderSimple_Toggle.shader

 ⑩ DynamicBranchShaderComplex_Toggle.shader

 ⑪ IfOnlyShaderSimple_Toggle.shader

 ⑫ IfOnlyShaderComplex_Toggle.shader

 

こちらは先程の①~⑥と基本的には同じですが、キーワードを別々に Toggle で定義しています。

今回のコード内では重複するような処理にはなっていませんが、

KeywordEnum と違い別々に定義しているので AAA も BBB も CCC も有効な場合もあれば

全部無効な場合など組み合わせの総数は大きく異なります。

 

⑧ MultiCompileShaderComplex_Toggle.shader


測定結果

こちらがそれぞれの測定結果の比較のまとめです。

(MemoryProfiler と RenderDoc の数値を切り貼りしています)

 

念の為、GraphicsAPI を OpenGLES 3.0 と Vulkan でビルドして比較もしています。

 

● KeywordEnum でキーワード定義バージョン

●Toggle でキーワードを定義バージョン

 

下記はターゲットプラットフォームを OpenGLES 3.0 にしてコンパイルしたコードの比較で、

「_VAR_TEST_AAA」というキーワードがONになっている場合の部分を抽出しています。

dynamic branch で定義している方は "UnityDynamicKeywords"  という Constant Buffer が定義されており、

そこに定義されている定数を使って if 文で分岐するような書き方になっていました。

 

 

↑ は Simple バージョンのコンパイル後のコードの WinMerge での比較画像で、

↓ は Complex バージョンのコンパイル後のコードの WinMerge での比較画像です。

 


今回の結論について

「KeywordEnum でキーワード定義バージョン」と「Toggle でキーワードを定義バージョン」とでは

If Only Shader を除けば GPU の処理時間的にはそこまで大差はありませんでしたが、

肝心な dynamic branch バージョンと multi compile バージョンの処理時間の差はかなりありました。

 

といいますか、dynamic branch バージョンは if only バージョンより少し高速なだけなのに、

メモリーサイズを比較するとキーワードで分岐している分 コードが肥大化してかなり差がありますね…

 

以上の点から、少なくとも Android 向けについては dynamic_branch を使うメリットはなさそうに思いました。

 

しかし、今回 Android 向けにしかテストしていないので、もしかしたら Android 以外のプラットフォームや

Shader の内容にもよってはメリットが出てくるのかもしれません。

 

今回の検証でおかしい点や、逆にこういう Shader では dynamic_branch を使うメリットがあったなど

もしありましたらご連絡頂けましたら幸いです。