【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