【Unity】SRP Batcherについて勉強してみる

2022/09/19に公開

はじめに

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 メモリについて触れて見ようと思います。

シェーダープロパティ

自作シェーダーにて、以下のようなプロパティを定義することを考えてみます。

MyShader.shader
Properties
{
    _Value ("Value", Float) = 0.7
}

CPUメモリとGPUメモリ

CPUとGPUはメモリを持っています。
通常、CPUとGPUのメモリは分断されており、GPUからはCPUメモリ上のデータは見えません。

マテリアルのパラメータはCPUに載る

マテリアルのパラメータは、CPUメモリ上に載ります。

次に、定義したプロパティ _Value を参照するようなシェーダーを書いてみましょう。
frag 関数から、変数 _Value を参照しています。

MyShader.shader
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)を定義する必要があります。

MyShader.shader
+ 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 (通常のバッチ処理)

通常のレンダリングでは、以下のような処理が行われます。

  1. オブジェクトの情報をGPUへアップロード (Generic CBUFFERに格納)
  2. マテリアルの情報をGPUへアップロード (Generic CBUFFERに格納)
  3. マテリアルとデータをBindする
  4. オブジェクトとデータをBindする
  5. ドローコールを発行

オブジェクト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 に格納されると考えられます。

shaderlab
CBUFFER_START(UnityPerMaterial)
float _Value;
CBUFFER_END

パフォーマンスの違いを見てみる

Unity公式が、SRP Batcherのベンチマーク用のUnityプロジェクトを公開しています。

こちらのUnityプロジェクトを利用して、SRP Batcherオン・オフのパフォーマンスを比較してみました。

https://github.com/Unity-Technologies/SRPBatcherBenchmark/

環境

  • 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処理時間が下がるようです。

関連 : アイデア次第で輝く!Unity・SRPの無限の可能性 - CEDiL

Discussion