Compute Shaderについて

vvvvのDX11環境で使えるシェーダーの中にはVertex, Geometry, Pixelなどいくつかのコンテクストがありますが、Compute Shaderは、GPUで数値計算をするためのものです。

シェーダーをある程度触ったことのある人ならばわかるとおもいますが、GPUの処理の特徴として単純な計算をひたすら繰り返すのがやたらと高速、というのがあります。
Vertex Shader, Geometry Shader, Pixel Shaderなどもその恩恵をうけてだいぶ高速に色々できるのですが、

Compute Shaderの他にはないユニークな点として、GPU内のメモリの読み書きが簡単にできる、というものがあります。これは例えば以前のフレームの状態を引き継いだシミュレーションのようなプログラムがサクっとできる、ということにつながります

…もっと他にCompute Shaderならではな利点はあるとおもいますが、とりあえずやってみるか～って時に一番わかりやすかったので、[こういうパーティクルエンジン](https://www.instagram.com/p/BN69YxCDvY7/)を作ってみました。

作ってみての感想として、非常におもしろかったのですが、色々ハマりました。。。

というかそもそも初学者向けの導入の記事が全然みあたらんかったな… というのがあったので、Compute Shaderやってみたいんだけどな… という人がこれまでよりもいいスタートを切れるようにと、この記事を書いています。

できるだけ簡単に書こうと思っていたのですが、もはやかなりハードコアなエリアで簡単には書けないなと思ったので、どうせならとなるべく詳細に、簡潔に書くように努めました。それでは、よろしくどうぞ…

---


## Compute Shaderを使う時に出てくる基本ノード

**Renderer (DX11 Buffer XXX)**
普通の`Renderer (DX11)`や`Renderer (DX11 TempTarget)`などと違って、画像ではなく処理したデータをGPUメモリに書き出すためのもの

**Dispatcher (DX11.Drawer)**
Compute Shaderで計算するにあたって、どのぐらいの並列度で計算するかを決めるノード。多いほど並列度が高くなる(多分)

**ReadBack (DX11.Buffer XXX)**
計算した結果はGPUのメモリに乗っているので、それをCPUに持ってくるためのノード。CPUにもってこれればIOBoxなどで確認できたりする

## 一番簡単なCompute Shaderのパッチ

一番シンプルと思われる構成のパッチがこれです。関係があるものは同じ色でマーキングしてあります

![00.jpg](https://qiita-image-store.s3.amazonaws.com/0/38051/b0d5aae7-823a-333b-47ab-eb8c7d58bf53.jpeg)

**水色: ユーザー定義変数**

ソースコード側で定義した変数は自動的にノードのピンができてパッチ側からいじれるようになる。
この場合だとLFOで定期的にランダムな値を入力している。

**紫色: `Dispatcher`と`numthreads`の関係**

`Dispatcher`の`Thread X`には1が指定されている。
これはシェーダーファイルの`[numthreads(4, 1, 1)]`の部分と関連が深い。

たとえば、`Dispatcher`で`(1, 1, 1)`が指定されていてソースコードのほうの`numthreads`で`(4, 1, 1)`が指定されている場合、

```
(4 * 1) * (1 * 1) * (1 * 1) => 4スレッド
```

の処理が行なわれることになる。なので`Readback`で読み出してきた値も4要素のみが更新されている。

`SV_DispatchThreadID`のセマンティクスがついた引数には現在処理中のスレッドIDが入ってくるので、それを使って`Output`にインデクスを指定して値を格納していく

**オレンジ: `Element Count`とバッファサイズの関係**

`Renderer`の`Element Count`の数は`Renderer`側で用意されるバッファサイズに相当する。

なので、`ReadBack`で読んできた値も10要素のスプレッドになっている。

**緑: `Stride`と`BACKBUFFER`に指定した型のサイズ**

vvvvでは、`BACKBUFFER`のセマンティクスをつけたやつが出力されるバッファになる。
**`BACKBUFFER`はソースコード中で一個でなくてはいけない**

また、`Renderer`の`Stride`には出力する型のバイト数を指定しないといけない。例えば:

```
struct MyStruct {
	float3 Position;
	float3 Velocity;
	float4 Color
};
```

のようなユーザー定義構造体を指定する場合には、`(3 + 3 + 4) * sizeof(float) =  40`なので、40を指定する。(OpenGLのCompute Shaderと違ってアライメントは考慮しなくていいみたい。単純にパックされたサイズでOK)

**赤: 実行する処理を指定**

ここは見ての通りなのですが`Technique`のピンで実行する処理を指定できます。

同じソースコードに複数の処理を書いておいて、`Technique`のピンの指定違いで別の処理を走らせたり、`Group`でまとめた複数の処理を一度に走らせるといったこともできます。

## 任意の回数の処理を走らせる

さきほどの例だと処理の回数が`numthreads`に指定していた4の倍数分でしか処理を走らせられなかった

こういう時に5とか9とか、中途半端な数分のループを走らせたい時には別途何回処理を走らせたいかの変数をシェーダーに入れてオーバーランしないような処理を入れるのが定石らしい。

![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/38051/3b027ab1-cac7-0b5c-9b2a-f1daf8ce16d3.jpeg)


**緑色: `Count`を追加**

新しく変数として`Count`を追加して、`SV_DispatchThreadID`の値がそれより大きければ何もしないでリターンする処理を入れている。

この一文によって安全でないメモリ領域に書き込む(=バッファオーバーラン)を回避して変なバグを生まないようにしている。

**黄色: `Dispacher1D`を追加**

既に説明したようにCompute Shaderの処理は(`Dispacher`で指定した数) * (`numthreads`で指定した数) 分走ることになるので、`Count`で指定した数より十分に大きい回数の処理を行うためにはグループサイズをどれぐらいにしたらいいのか？ の計算をここのサブパッチでしている。

**赤: `ReadBack`の結果**

`Count`で指定した数ぶんの処理が走って、スプレッドの数もそれと同じになっている所に注目

とりあえず、これで任意の回数の処理を走らせることができる。

もちろん毎回`Count`と`dtid.x`の比較とif分岐が計算される事になるので汎用性は高まったが効率は悪くなっていることに注意。

設計的に、一回の処理で`numthreads`で指定した回数の処理が走っても問題ない事にできる場合はそちらの方が効率がいいはず

## `RWByteAddressBuffer`を使う

今までは簡単のため`RWStructuredBuffer`を使っていたが、実はこの型はCompute ShaderとPixel Shaderでしか使えない。

**追記**
よくよく考えたら [StructuredBuffer](https://msdn.microsoft.com/ja-jp/library/ee422384.aspx) を使うと全部のシェーダーから`StructuredBuffer<T>`の形でデータ読めますね？それを使ったら以下の内容もっと簡単にできるんだけど、、
`RWByteAddressBuffer`もたまに使わざるを得なくなるのでとりあえず残しておきます！
**/追記**

[RWStructuredBuffer](https://msdn.microsoft.com/ja-jp/library/ee422364.aspx)

Vertex ShaderやGeometry ShaderでCompute Shaderで計算した結果を使いたい場合は、`RWStructuredBuffer`よりもプリミティブな型である`RWByteAddressBuffer`を使う必要がある。

![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/38051/ad1d8d95-f2eb-6c44-c1f8-99938e3ad46a.jpeg)

**緑色: `RWByteAddressBuffer`を使うための変更**

`Renderer (DX11 Buffer)`を、`Renderer (DX11 Buffer.Raw)`に変更する。今まで`RWStructuredBuffer`を使う場合はは`Stride`と`Count`を入れていたが、ここでは`Stride` * `Count`をした値を確保すべきバイトサイズとして設定する。

ソースコードで`RWStructuredBuffer<T>`を指定していた所を`RWByteAddressBuffer`に変更。ここで型の指定子がなくなっていることに注意 → C++で言う所の`std::vector<T>`と`malloc`等で確保した単純なバイト列の違いのようなかんじ

また、値の読み書き時に今までは`Output[N]`のようにインデックスを指定していた所を`Load`, `Store`などのメソッドに変更する必要がある。

それぞれバイトオフセットを指定するんだけど、`uint`型しか扱えない事になっているので浮動小数点を使いたい時はそれぞれ`asfloat`、`asint`、`asuint`などで型変換する。

詳しくはこちら → [RWByteAddressBuffer](https://msdn.microsoft.com/ja-jp/library/ee422345.aspx)

とりあえずそのあたりの変更によって`RWByteAddressBuffer`を出力することができるようになる。また、IOBoxなどで内容を確認する時は`ReadBack (DX11.Buffer Raw)`を使う。

## VertexShaderで`RWByteAddressBuffer`を使う

![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/38051/0adfab23-d481-5cc4-fb87-1c968788c7fb.jpeg)

出力した`RWByteAddressBuffer`を`GeomFX`に入れてジオメトリを生成する。

入力のジオメトリとして`NullGeometry (DX11.Drawer)`を指定して、バーテックスシェーダーの入力値として`SV_VertexID`をインデックスとして使う。

上の画像は`SV_VertexID`のインデックスのメモリを`Load`で読んできてジオメトリのY軸のデータとして使っているという例です。

## まとめ

最後心が折れてだいぶ駆け足になってしまいました。。

別段これだけならばVertex Shaderだけでも全然いけるのですが、、

毎フレームちょっとづつ値を変化させるシミュレーションのような処理など、今までのシェーダーでは結構ややこしいことをしないとできなかったタイプの処理がサクっとできるので、Compute Shader、楽しいですよ…！

今回紹介していたパッチなど、こちらです → https://cl.ly/3L3l072d213H

興味が出てきた方は、このあたりも参考にしてください！↓
[Instance Noodles](https://vvvv.org/contribution/instance-noodles)
[VVVV.GPUParticle](https://github.com/satoruhiga/VVVV.GPUParticle)