UNITY DrawCall調査  GPU Instancing ~ UNITY2018.3.6f1 ~

UNITYのパフォーマンス向上には、DrawCall削減という話はよく聞くと思うし、いろいろな手法が書いてあるが
実際に手で動かさなければ納得できないので、実際に調査です
手抜きで ビルトインのStandardShaderで行います

#DrawCall、SetpassCall、TotalBatchesって何？
##SetPass
マテリアルの変更のためにGPUへグラフィックコンテキストを送る回数です
グラフィックコンテキストの変更はとてもコストが大きいため
この数を減らすことが最重要
基本的にはマテリアル数を減らす事で下げます

##DrawCall
これはGPUに対して頂点の描画命令を出した回数です
同じマテリアルでも異なるオブジェクトの場合はDrawCallが呼ばれる事もあるので
DrawCall≧SetPass  になるでしょう

##TotalBatches
なんだろうね


#素のScene

UNITYを新規で作り実行すると下記の画面になります
![0.PNG](https://qiita-image-store.s3.amazonaws.com/0/13916/57ac4a21-01cd-c48a-be43-6c621cc1cfba.png)

これをプロファイリングしましょう
Windows->Analytics->Profiler でプロファイル表示

Renderingをみます

```
SetPass Calls: 2   	Draw Calls: 2 		Total Batches: 2 	Tris: 1.0k 	Verts: 4.1k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
Used Textures: 2 - 1.0 KB
RenderTextures: 9 - 23.4 MB
```

何もしてないのに SetPass、DrawCallが2 ？？
SkyBoxですね！

ってことで

#SkyBox消して本当に何もない世界

![SnapCrab_NoName_2019-2-27_18-44-11_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/bdb67a22-4ea4-4097-7e9d-ae2c37dfccdd.png)

カメラのSkyBox削除したりRendering設定のSkybox消したり
で、本当の無ができました

```
SetPass Calls: 0   	Draw Calls: 0 		Total Batches: 0 	Tris: 0 	Verts: 0
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
Used Textures: 0 - 0 B
RenderTextures: 7 - 16.3 MB
```

本当の無ができました

#Cube1個表示
画面にCube1個置きます

![SnapCrab_NoName_2019-2-27_19-15-0_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/32f0b851-d1a7-687e-a671-3bcc2e0977c4.png)

```
SetPass Calls: 5   	Draw Calls: 5 		Total Batches: 5 
```


おや？5個？
影の設定を消します

```
SetPass Calls: 2   	Draw Calls: 2 		Total Batches: 2 
```

2?
よくわかりませんが、2個（Standardはマルチパスなのかな？）

#同じマテリアルのCube4個
当然全部影をOFFにしておきます

![SnapCrab_NoName_2019-2-27_19-27-17_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/8f362b16-096d-15c5-18e1-d07c44e09f33.png)

```
SetPass Calls: 2   	Draw Calls: 2 		Total Batches: 2 
```

同じマテリアルはUNITYでは可能な限りまとめて送ってくれるので（CPUバッチング）
2のままですね

#スフィア1個

```
SetPass Calls: 2   	Draw Calls: 2 		Total Batches: 2 
```
Cubeと変わらず2回

#スフィア4個

```
SetPass Calls: 2   	Draw Calls: 8 		Total Batches: 8 
```

スフィアは頂点数が多いので、まとめてくれません
ので DrawCallは8です（2x4）
が、マテリアルは1個なので SetPassは2ですんでます


#色違いのマテリアルをエディタ上で作成しアタッチ
4個のうち2個を色違いのマテリアルを作り設定

4![SnapCrab_NoName_2019-2-27_19-39-17_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/30e61f31-da1e-0c2a-5403-3553094e94ea.png)

```
SetPass Calls: 4   	Draw Calls: 4 		Total Batches:  
```

4になった

#同じマテリアルを使いスクリプトでシェーダーカラーを変更

4つのCubeに下記のスクリプトを設定し、そのうち2個をRedにする

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class materialize : MonoBehaviour
{
    public Color color = Color.white;
    private Material mat = null;

    void Start()
    {
        Renderer renderer = GetComponent<Renderer>();
        mat = renderer.material;
    }

    void Update()
    {
        mat.SetColor("_Color", this.color);
    }
}
```

```
SetPass Calls: 8   	Draw Calls: 8 		Total Batches: 8 
```

超増えた！！
どうやら全部別々のマテリアルだと認識するようです
GPU Instancingしてもかわらない

#MaterialPropertyBlockを使う

上記のスクリプトを変更

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class materialize : MonoBehaviour
{
    public Color color = Color.white;
    private Material mat = null;
    private MaterialPropertyBlock materialPropertyBlock = null;
    private Renderer renderer = null;

    void Start()
    {
        materialPropertyBlock = new MaterialPropertyBlock();
        renderer = GetComponent<Renderer>();
    }

    void Update()
    {
        materialPropertyBlock.SetColor(id, color);
        renderer.SetPropertyBlock(materialPropertyBlock);
    }
}
```

```
SetPass Calls: 4   	Draw Calls: 4 		Total Batches: 4 
```

4になった（別マテリアルと同じ）

GPU Instancingを4つのCubeに設定

```
SetPass Calls: 3   	Draw Calls: 3 		Total Batches: 3 	Tris: 0 	Verts: 0
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 8 	Batches: 3 	Tris: 0 	Verts: 0
Used Textures: 3 - 0 B
RenderTextures: 8 - 17.3 MB
```

GPU Instancingも効き 3になった
MaterialPropertyBlockでは、マテリアルを複製せず、同じマテリアルとして設定だけ変更してくれるようです

#いっぱい表示してみよう

Updateで毎フレーム CubeをInstancingし
ランダムで色をつけてランダムな方向に飛ばすパーティクルもどきを作り
負荷を比較する

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Shoot : MonoBehaviour
{
    public GameObject go;

    // Update is called once per frame
    void Update()
    {
        var obj = Instantiate(go);
        var rigidbody = obj.GetComponent<Rigidbody>();
        var script = obj.GetComponent<materialize>();
        var col = new Color( Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f));
        script.color = col;

        rigidbody.AddForce( Random.Range(-20.0f, 20.0f), Random.Range(3.0f, 50.0f), Random.Range(-20.0f, 20.0f), ForceMode.Impulse);
    }
}

```

![SnapCrab_NoName_2019-2-28_3-0-39_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/de592333-52f2-4c41-2161-4b22eddd5dd7.png)


##MaterialPropertyBlock不使用＆GPU Instancing OFF

```
SetPass Calls: 370   	Draw Calls: 370 		Total Batches: 370 	Tris: 4.1k 	Verts: 8.2k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0 
```

400付近。Batchなし

##MaterialPropertyBlock不使用＆GPU Instancing ON

```
SetPass Calls: 358   	Draw Calls: 358 		Total Batches: 358 	Tris: 4.1k 	Verts: 8.2k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 358 	Batches: 358 	Tris: 4.1k 	Verts: 8.2k
```

Batchにはなっているが、Drawcall減らず

##MaterialPropertyBlock使用＆GPU Instancing OFF

```
SetPass Calls: 348   	Draw Calls: 348 		Total Batches: 348 	Tris: 4.1k 	Verts: 8.2k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
```

あまり変わらず

##MaterialPropertyBlock使用＆GPU Instancing ON

```
SetPass Calls: 197   	Draw Calls: 197 		Total Batches: 197 	Tris: 4.1k 	Verts: 9.2k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 392 	Batches: 197 	Tris: 4.1k 	Verts: 9.2k
```

GPU Instancingが効いて、全てのDrawが約400に対して、SetPass、DrawCallは200と半分に！

・・・予想ではSetPassが20ぐらいまで下がるはずだったんだけど思ったほどさがらなかった
どこかにミスあるのか？

##おまけ、GPU Instancing ONで同じ色だけ描画

```
SetPass Calls: 4   	Draw Calls: 4 		Total Batches: 4 	Tris: 4.1k 	Verts: 9.2k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 392 	Batches: 4 	Tris: 4.1k 	Verts: 9.2k
```

400のDrawに対して SetPass4。完全に GPU Instancing効いてる！

#まとめ
UNITYは同じマテリアル&設定のモデルは可能な限りまとめて表示してくれる（CPU バッチング）
スフィアのような頂点数が多いものはバッチング出来ない（がマテリアルが同じであればSetPassは増えない）
異なるマテリアル、パラメータの異なるマテリアルはバッチング出来ない
MaterialPropertyBlockを使えばパラメータの異なるマテリアルでも新たにマテリアルが作成されないようだ
GPU Instancingに対応したシェーダを使う事でSetPass CallやDrawCallを削減できる



で、納得いった？？
いや、絶対納得しないよね！

という事でシェーダー作る事にした

#シェーダー作成
Instancingというシェーダーを作った

```cpp
Shader "Instancing"
{
	Properties
	{
		_Color("Color", Color) = (1, 1, 1, 1)
	}
		SubShader
	{
		Tags { "RenderType" = "Opaque" "DisableBatching" = "True"}

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_instancing

			#include "UnityCG.cginc"
			#include "UnityStandardParticleInstancing.cginc"

		struct appdata
		{
			float4 vertex : POSITION;
			float4 color : COLOR;
			UNITY_VERTEX_INPUT_INSTANCE_ID
		};

		struct v2f
		{
			float4 color : TEXCOORD1;
			float4 vertex : SV_POSITION;
		};


		UNITY_INSTANCING_BUFFER_START(Props)
			UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
		UNITY_INSTANCING_BUFFER_END(Props)

		v2f vert(appdata v)
		{
			UNITY_SETUP_INSTANCE_ID(v);

			v2f o;
			o.vertex = UnityObjectToClipPos(v.vertex);
			o.color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);

			return o;
		}

		fixed4 frag(v2f i) : SV_Target
		{
			return i.color;
		}

			ENDCG
		}
	}
}
```

_Colorというプロパティに対してInstancingを行う何の変哲もないシェーダーだ

> pragma multi_compile_instancing
UNITY_VERTEX_INPUT_INSTANCE_ID
        UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
UNITY_SETUP_INSTANCE_ID(v);
 UNITY_ACCESS_INSTANCED_PROP(Props, _Color);

のあたりがキーとなる

そもそもGPU Instancingとは、GLSLやHLSLのインスタンシングである

http://maverickproj.web.fc2.com/d3d11_18.html

それをUNITYは上記のマクロを使い非常に巧妙にマルチプラットフォームに対応させている
コード見れば見るほど素晴らしさがわかる！

コードの説明を行おうとおもったが、凹みさんが丁寧に解説してるので紹介にとどめる

http://tips.hecomi.com/entry/2018/09/24/232125


とりあえず上記のコードで、GPU Instancingが可能になったので測定する

まず、GPU Instancingチェックなし

```
SetPass Calls: 191   	Draw Calls: 191 		Total Batches: 191 	Tris: 2.0k 	Verts: 4.1k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
```

DrawCall 約200。今までの半分なのはやはり、Standardのシェーダーは2パス走っていたのだろう
（ライト1個、影なしだけど、何のパスだろうか？？ ForwardAddかShadowCasterが怪しい）


そして、GPU Instancingあり

```
SetPass Calls: 1   	Draw Calls: 1 		Total Batches: 1 	Tris: 2.0k 	Verts: 4.1k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 207 	Batches: 1 	Tris: 2.0k 	Verts: 4.1k
```

ｷﾏｼﾀﾜｰｰｰｰ!!

200 Drawですが、1マテリアルなのでSetPass Callは1回で、全部GPU Instancing化された！

#念のためスフィアも

GPU Instancing無し 

```
SetPass Calls: 207   	Draw Calls: 207 		Total Batches: 207 	Tris: 158.7k 	Verts: 106.5k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
```

GPU Instancing無し

```
SetPass Calls: 1   	Draw Calls: 1 		Total Batches: 1 	Tris: 157.7k 	Verts: 105.5k
(Dynamic Batching) 	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Static Batching)    	Batched Draw Calls: 0 	Batches: 0 	Tris: 0 	Verts: 0
(Instancing)    	Batched Draw Calls: 206 	Batches: 1 	Tris: 157.7k 	Verts: 105.5k
```

CPUバッチングでは、頂点数（正確には頂点要素数）が多いとバッチング出来ないので
スフィアは個別にDrawCallが走っていたが
GPU Instancingでは やはり DrawCallは1回で終わる！！
これが、CPUバッチングは多ポリゴンメッシュに弱いが、GPU Instancingはハイポリゴンに強いというやつだ

そうそう、これを期待してた！！
Standardシェーダーが、DrawCallが半分にしかならなかった理由は
おそらく、シェーダーが2パスあり、1つのパスはGPU Instancingが効いて激減したが
もう1つのパスがGPU Instancingに対応しておらず、半分という結果だったと思われる

やっと寝れる


追記
#ShadowCaster
最初に謎のパスはやはりShadowCasterパスだった。
ShadowCasterパスを書かなければForwardBassのみの1パスだった

先ほどのシェーダにShadowCasterパスを追加

```cpp
		Pass
		{
			Name "SHADOW_CASTER"
			Tags{ "LightMode" = "ShadowCaster" }

			Lighting Off
			ColorMask RGB
			Fog{ Mode Off }

			Offset 1, 1
			Cull Off

			ZWrite On ZTest LEqual

			CGPROGRAM
			#pragma target 3.0
			#pragma vertex vertShadowCaster
			#pragma fragment fragShadowCaster

			#include "UnityCG.cginc"

			struct v2f {
				V2F_SHADOW_CASTER;
			};

			v2f vertShadowCaster(appdata_base v) {
				v2f o = (v2f)0;
				TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
				return o;
			}

			float4 fragShadowCaster(v2f i) : SV_TARGET{
				SHADOW_CASTER_FRAGMENT(i)
			}
			ENDCG
		}
```

すると結果は
![SnapCrab_NoName_2019-3-5_2-30-5_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/91cae0ee-18f6-adc4-ffb0-0ed50c8c3bf2.png)

最初のStandardシェーダのように　177パス増えた
このうちわけは、177のGPU InstancingされたForwardBaseパスと、 Instancingされない177のShadowCasterパスが動いている

ShadowCasterもGPU Instancingしたいよね？

#GPU Instancing

```cpp
			#pragma multi_compile_instancing
```

を追加するだけで、ShadowCasterも GPU Instancing出来る
（UNITY_VERTEX_INPUT_INSTANCE_ID　等は本当に不要なんだろうか？

![SnapCrab_NoName_2019-3-5_2-37-33_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/13916/3b317b16-1bd5-9d71-e1c5-a716b425cbb1.png)
）

とりあえず、ShadowCasterパスも Instancing有効になった