【Unity】SRP Batcherについて勉強してみる
はじめに
SRP Batcher への理解を深めることを目的として、本記事を執筆したいと思います。
参考リンク
本記事を執筆するにあたって、下記のリンクを参考にさせていただきました。
Scriptable Render Pipeline Batcher - Unity - Manual
SRP Batcher:レンダリングをスピードアップ
How the SRP Batcher works
【Unity】SRP Batcherまとめ - Dynamic BachingやGPUインスタンシングとの違い~シェーダの書き方まで
SRP Batcherとメモリ
SRP Batcherは、マテリアルのデータをGPU内にキャッシュすることで、CPUのレンダリング負荷を下げるものです。
SetPass Callsを削減できます。
ここでは、SRP Batcherへの理解を深めるため、CPU メモリ ・ GPU メモリについて触れて見ようと思います。
シェーダープロパティ
自作シェーダーにて、以下のようなプロパティを定義することを考えてみます。
Properties
{
_Value ("Value", Float) = 0.7
}
CPUメモリとGPUメモリ
CPUとGPUはメモリを持っています。
通常、CPUとGPUのメモリは分断されており、GPUからはCPUメモリ上のデータは見えません。
マテリアルのパラメータはCPUに載る
マテリアルのパラメータは、CPUメモリ上に載ります。
次に、定義したプロパティ _Value
を参照するようなシェーダーを書いてみましょう。
frag
関数から、変数 _Value
を参照しています。
float _Value;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
return _Value;
}
シェーダーからは、CPUメモリの0.7という数値を見ることができません。
0.7をGPUメモリに転送することで、シェーダーから0.7が見えるようになります。
GPUメモリ上には、constant buffer (cbuffer) という領域が確保され、その中に0.7という数値が入ります。
シェーダーをSRP Batcherに対応させる
自作シェーダーをSRP Batcherに対応させたい場合、UnityPerMaterial というCBUFFER (constant buffer)を定義する必要があります。
+ CBUFFER_START(UnityPerMaterial)
float _Value;
+ CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
float4 frag (v2f i) : SV_Target
{
return _Value;
}
そもそも、CBUFFERとは何なのでしょうか?
CUBFFERについて掘り下げていきたいと思います。
CBUFFERとGPUメモリ
CBUFFER (constant buffer) とは、GPUメモリ上に確保されるメモリ領域のようなもので、
シェーダーで使用するデータを入れておくことができます。
DirectXでは定数バッファとも呼ばれたりするようです。
float _Value;
などのシェーダーで使用する数値(プロパティ)は、GPUのCBUFFERの中に格納さます。
レンダリングの際、CPUメモリからCBUFFERへデータのコピーが行われます。
Standard Batch(従来のバッチ処理)と、SRP Batch(SRP Batcherによるバッチ処理) の2種類が存在します。
Standard Batch (通常のバッチ処理)
通常のレンダリングでは、以下のような処理が行われます。
- オブジェクトの情報をGPUへアップロード (Generic CBUFFERに格納)
- マテリアルの情報をGPUへアップロード (Generic CBUFFERに格納)
- マテリアルとデータをBindする
- オブジェクトとデータをBindする
- ドローコールを発行
オブジェクト1個を描画するたびに、上記の処理が行われます。
マテリアルやオブジェクトの数が増えてくると、アップロード(SetPass Calls)の回数が増え、CPU負荷が増加します。
1500個のオブジェクトがシーンに存在する場合、
SetPass Callsが1500回発生し、CPUに高い負荷がかかります。
FrameDebugger
SRP Batch (SRP Batcherによるバッチ処理)
SRP Batcherでは、マテリアルのデータをGPU内にキャッシュ(永続化)してくれます。
マテリアルのデータが変更されたときだけ、マテリアルに対応するCBUFFERを作り直します。
結果として、SetPass Calls(データのアップロード回数)が減るのでCPU側のレンダリング負荷が下がります。
FrameDebugger
CBUFFER(UnityPerMaterial)
~ CBUFFER_END
で囲んだデータは、GPU上の Material CBUFFER に格納されると考えられます。
CBUFFER_START(UnityPerMaterial)
float _Value;
CBUFFER_END
パフォーマンスの違いを見てみる
Unity公式が、SRP Batcherのベンチマーク用のUnityプロジェクトを公開しています。
こちらのUnityプロジェクトを利用して、SRP Batcherオン・オフのパフォーマンスを比較してみました。
環境
- Unity2021.3.9f1
- Universal RP 12.1.7
比較 (Statistics)
Unityのプロファイラーを利用して、レンダリングStaticticsの違いを見てみます。
条件 | SetPass Calls |
---|---|
SRP Batcherオフ | 1.4k |
SRP Batcherオン | 28 |
SetPass Calls 以外の数値に大きな違いは見られませんでした
比較 (CPU負荷)
SRP Batcher オフ
処理時間 : 約15ms
SRP Batcher オン
処理時間 : 2.31ms
処理負荷を見てみる
SRP Batcher オフ
RenderPipelineManager.DoRenderLoop_Internal の CPU処理時間は 15.26ms でした
SRP Batcher オン
RenderPipelineManager.DoRenderLoop_Internal の CPU処理時間は 2.16ms でした
まとめ
条件 | SetPass Calls | CPU処理時間 |
---|---|---|
SRP Batcherオフ | 1.4k | 15.26ms |
SRP Batcherオン | 28 | 2.31ms |
オブジェクトの数が多いほど、SRP Batcherを適用した際にCPU処理時間が下がるようです。
Discussion