【Unity】Draw indirect argument buffer too small. Must be at least XX bytes というエラーが出たときの対処法【ConputeShader】

本記事は Unity Advent Calendar 2023 4日目の記事です。
[3日目:CanvasMaterialでUI Shaderをシンプルに](https://qiita.com/up-hash/items/dec16eeae94513799cd9) @up-hash さん << ココ >>　[５日目:unsafeでアンマネージドな構造体でポインタアクセスとポリモーフィズムのテスト](https://kenken5050.hatenablog.com/entry/2023/12/05/002046) @fukaken5050 さん


# TL;DR
- ComputeShader/ComputeBuffer を使う時はUnityのサンプルを読もう
- ComputeShader/ComputeBuffer 周りはググってもChatGPTも答えてくれないことが多いので、覚悟を持って挑もう
- C#のここの行がエラーとは示さないのでエラー文から原因を推測する力が必要です


# エラーの経緯

業務て点群のデータ(.ply, .e57等) をUnityで読めるプログラムを制作している際、Particleとして点群を描画するため [Graphics.DrawMeshInstancedIndirect関数](https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html) を使っていました。

Mac (Editor / MacOS App) では問題なく表示できたのですが、Windows 環境 (Editor/App) では `Draw indirect argument buffer too small. Must be at least 20 bytes (5ints).` という謎のエラーが表示され、点群が描画できないという事象にぶつかりました。

もちろんググっても英語のページすらヒットしない状況。

ChatGPT に聞いても
![スクリーンショット 2023-11-29 15.54.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65257/9745b6de-a4cc-30cb-1f98-53575492af5e.png)
といった具合に当たり障りのない、ちょっと気を利かせた翻訳程度くらいの情報しか得られません。

# エラーの原因

`Graphics.DrawMeshInstancedIndirect` API では引数に `Mesh情報`, `Material情報`, 'ComputeBuffer` 等が要求されます。

この `ComputeBuffer` ですが、GPGPUに日常的に触れている方いがいはほとんど馴染みのないClassだと思います。
ここのComputeBufferの設定パラメータミスによって今回のエラーが発生していることがわかりました。

## ComputeBuffer
ComputeBuffer は名前の通りComputeShader で読み書きするようのデータ置き場のことです。

ComputeBufferは[コンストラクタ](https://docs.unity3d.com/ScriptReference/ComputeBuffer-ctor.html)で初期化するのですが、そのときの引数として以下の情報を与える必要があります。

| パラメータ名 | 説明 | 
|--- |--- |
| count	| Bufferに格納する要素数。 1: スカラー, 1次元配列, 2: ２次元配列 ... |
| stride | Bufferに格納される要素１つあたりのサイズ[byte] <br>ただし2048 未満の4の倍数、つまりは int/uint/float =4byte * 配列長さ の値を入れればOK|
| type | [Buffer の種別定義](https://docs.unity3d.com/ScriptReference/ComputeBufferType.html<br> DrawMeshInstancedIndirect を使う場合は `ComputeBufferType.IndirectArguments` 一択 |
| usage | 省略可能. Bufferの利用用途定義 |

ここのstrideは基本的に事前に作成した int[]/uint[]/float[] のLength * sizeof(int)/sizeof(uint)/sizeof(float) って感じでやれば問題ないです。


## Draw indirect argument buffer too small. Must be at least XX bytes 
エラー文の字のごとく、Bufferサイズが小さいというエラーです。

今回は `Must be at least 20 bytes (int5s)` とあり、int[5]以上のBufferがないといけないとのことでした。

```ComputeBuffer.cs

void Initialize()
{
    ...

    
    var args = new uint[4]
    {
        particleMesh.GetIndexCount(0),
        (uint) particles.Length,
        particleMesh.GetIndexStart(0),
        particleMesh.GetBaseVertex(0),
    };
    var argBuffer = new ComputeBuffer(
        1,
        sizeof(uint) * args.Length,
        ComputeBufferType.IndirectArguments
    );
    argBuffer.SetData(args);

    ...
}

```

みたいな形で必要なパラメータをargs変数に定義してそれを渡していたのですが、どうやら動かすプラットフォームによって必要な最小バッファサイズが異なるようです。

MacOSXであれば問題なく描画できたのですが、Windowsだと描画に失敗しました。

ちなみに `Graphics.DrawMeshInstancedIndirect` のサンプルを見てみると

```Graphics.DrawMeshInstancedIndirect.Sample.cs


    private int cachedInstanceCount = -1;
    private int cachedSubMeshIndex = -1;
    private ComputeBuffer positionBuffer;
    private ComputeBuffer argsBuffer;
    private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };

```
のように利用しなくても、 `uint[5]` で定義しているので、基本的にはサンプルに則って作っておくのがよさそうです。


# まとめ
- ComputeShader/ComputeBuffer を使う時はUnity公式のサンプルをしっかり観察しよう
- ComputeShader/ComputeBuffer 周りはググってもChatGPTも答えてくれないことが多いので、覚悟を持って挑もう
- C#のここの行がエラーとは示さないのでエラー文から原因を推測する力が必要です
    - 今回だと描画ができない→Shader系
    - DrawIndirectの文字がある→ShaderコードではなくAPIを呼んでいる箇所
    - buffer 5ints とある→過去に動いていたコードと比較してint配列に差分がないか確認
        - あたりだった