Posted at

Compute Shader Compact解釈

皆さんこんにちは!先回は簡単なGPU Driven Cullingの原理を説明しましたが、ComputeShaderの部分は難しいですから、説明しない。今回は先回解釈しないCompute Shader部分を説明します。


Compute Shader

先ずComputeShaderというのもを説明します。ComputeShaderは通用計算のシェーダーです。テクスチャだけではなく、普通なバッファの訪問もできます。特別な関数もあります。

GPUには、たくさんコロールがあります。それから、パラレルでComputeShaderを実行することができます。ComputeShaderは対応のスレッドに実行するが、全て1回実行するわけではないです。GPUはスレッドをグループに入れって、たくさんグループをパラレル実行します。しかし、全てグループを一回だけに実行するもわけではない。それは注意点です。スレッドの詳しい実行方法はこちらです。


Compact

先回の問題また説明します。

image.png

今回の問題点はどのようなComputeShaderを使用してコンパクトします。説明は簡単になるために、会社の役割分担プランを使用して説明します。

image.png

まず皆さんは自分の対応の仕事をしました。その後はコンパクトします。つまり、皆さんが確認した書類をまとめます。


リーダー

簡単な方法は、リーダーさんはその後、一々でまとめます。他の社員さんは何もしない、休みます。

image.png

リーダーさんは「重い仕事ですね!」と思います。優しい先輩ですが、遅いです。そして、書類数量は多い場合は、あまり遅いです。

少し考えました、問題点は書類の転送です。皆さん、助けてください!!!


ボス

最近、先輩はボスになります。それから、働く方法も変えます。書類の番号を付けていれば、社員さんは自らによる書類を転送するもできます!

image.png

今回ボスの仕事は「計数」だけです。それは「ビッグボス」です。


 平等主義

最近は「社内平等」と考える社員も多いです。GPUのスレッドも平等です。ボスような人物はない。それから、ボススレッドは計数の仕事をしているでも、普通な社員の仕事も担当します。

じゃ、少し整理しましょう!

image.png

1. 皆さんは書類をチェックして、結果は自分に持っています。

2. スレッド0は、ボスになります。他のスレッドは待ちます。

3. ボスさんは、書類を統計して、番号を決まります。

4. 皆さんは、自分の番号をもらって、書類を転送します。


 ??主義

でも、今のGPUは、「Interlock」オペレーションをサポートしました。ボスじゃない場合も、役割はできます。

以前の場合は、たくさんスレッドは同じ場所のデータを変わりますと、間違ったデータを見ました。それはポピュリズムです。

でも「Interlock」では、実行のスレッドはいつだけです。データを変わって、そのデータの改変前の内容ももらいました。

それでは、皆さん同じ場所に「InterlockedAdd」して、番号も決まります。

image.png


 グループ

でも、実はGPUの全てスレッドは同じ場所のデータを改変することが少し無理です。

同じグループのスレッドはできます。少し残念ですね。この場合は、本社と分部の関係ようなものです。

それじゃ、グループのリーダーを選びます。リーダーさんは本社に会議を参加します。グループの中には、「Interlock」を使用して、自らによる番号を決めます。その後、リーダーさんは自分のグループの最大番号を持って本社の会議を参加します。本社は、Globalのオフセットを計算します。リーダーさんは本社の結果を持って、自分のグループを説明します。その後、スレッドはグローボとグループの番号に基づいて最後の番号を計算します。

image.png


 メモリバリア

でも、スレッドはリーダーを会社へ帰ることを待っていない、勝手に自分の番号を計算してデータを転送すれば、結果は間違いです。それからメモリバリアを使用します。

メモリバリアは、全てスレッドがここまで実行完成じゃないでは、この場所に待っています。メモリバリアを付けていれば、この感じです

image.png

それは最後のバージョンです。対応のコードはこれです

    GroupMemoryBarrierWithGroupSync();

if (Invalid == false && NeedCulling)
{
if (cullingData.Radius < 0.0f)
{
Inside = true;
}
else
{
Inside = FrustumCullingSphere(InputInstance, cullingData.Radius);
}
}
if (Invalid == false)
{
if (Inside || NeedCulling == false)
{
InterlockedAdd(localValidInstances, 1, localSlot);
}

}
GroupMemoryBarrierWithGroupSync();

if (threadId == 0 && Invalid == false)
{
InterlockedAdd(
outCommand[DrawArgsCount * 5 + 1],
localValidInstances,
globalSlot
);
}

GroupMemoryBarrierWithGroupSync();
//if (index == 0)
//{
// outCommand[DrawArgsCount * 5 + 1] = g_CommandCount;
//}
if (Invalid)
{
return;
}
if (NeedCulling == false || Inside)
{
uint target_pos = args.StartInstanceLocation + globalSlot + localSlot;
outInstances[target_pos * 4] = srcInstances[RealInstanceIndex * 4];
outInstances[target_pos * 4 + 1] = srcInstances[RealInstanceIndex * 4 + 1];
outInstances[target_pos * 4 + 2] = srcInstances[RealInstanceIndex * 4 + 2];
outInstances[target_pos * 4 + 3] = srcInstances[RealInstanceIndex * 4 + 3];
}

コードは少ないでも、原理は難しいそうです。じゃ、今回はそれそれです。

みんなさん、本当にありがとうございます!!!


参考資料

Life of a triangle - NVIDIA's logical pipeline

https://www.irasutoya.com/