4

ストリーム出力を使用する頂点シェーダーではなく、計算シェーダーを使用してボーン変形をメッシュ頂点に適用することを検討しています。コンピューティング シェーダーの実行が頂点シェーダーよりもはるかに遅いことがわかりましたが、書き留める前に、何か間違ったことをしていないことを確認したいと思います。

100,000 個の頂点と 300 個のボーンの 1,000 フレームのアニメーション データのテスト データを使用すると、頂点シェーダーは約 0.22 ミリ秒で実行され、計算シェーダーは 4 倍の 0.85 ミリ秒かかります。タイミングは、(CPU タイマーではなく) D3D API タイマー クエリを介して行われます。

変形構造体.hlsl

struct Vertex {
  float3 position : POSITION;
  float3 normal : NORMAL;
  float2 texcoord : TEXCOORD;
  float3 tangent : TANGENT;
  float4 color : COLOR;
};

struct BoneWeights {
  uint index;
  float weight;
};

StructuredBuffer<matrix> g_bone_array : register(t0);
Buffer<uint> g_bone_offsets : register(t1);
Buffer<uint> g_bone_counts : register(t2);
StructuredBuffer<BoneWeights> g_bone_weights : register(t3);

bone_deform_cs.hlsl

#include "deform_structs.hlsl"

StructuredBuffer<Vertex> g_input_vertex : register(t4);
RWStructuredBuffer<Vertex> g_output_vertex : register(u0);

[numthreads(64,1,1)]
void BoneDeformCS(uint id : SV_DispatchThreadID) {
  Vertex vert = g_input_vertex[id.x];
  uint offset = g_bone_offsets[id.x];
  uint count = g_bone_counts[id.x];

  matrix bone_matrix = 0;
  for (uint i = offset; i < (offset + count); ++i) {
    BoneWeights weight_info = g_bone_weights[i];
    bone_matrix += weight_info.weight * g_bone_array[weight_info.index];
  }

  vert.position = mul(float4(vert.position,1), bone_matrix).xyz;
  vert.normal = normalize(mul(vert.normal, (float3x3)bone_matrix));
  vert.tangent = normalize(mul(vert.tangent, (float3x3)bone_matrix));
  g_output_vertex[id.x] = vert;
}

bone_deform_vs.hlsl

#include "deform_structs.hlsl"

void BoneDeformVS(uint id : SV_VertexID, Vertex vsin, out Vertex vsout) {
  uint offset = g_bone_offsets[id];
  uint count = g_bone_counts[id];

  matrix bone_matrix = 0;
  for (uint i = offset; i < (offset + count); ++i) {
    BoneWeights bone_info = g_bone_weights[i];
    bone_matrix += bone_info.weight * g_bone_array[bone_info.index];
  }

  vsout.position = mul(float4(vsin.position,1), bone_matrix).xyz;
  vsout.normal = normalize(mul(vsin.normal, (float3x3)bone_matrix));
  vsout.tangent = normalize(mul(vsin.tangent, (float3x3)bone_matrix));
  vsout.texcoord = vsin.texcoord;
  vsout.color = vsin.color;
}

実行後のバッファの内容を比較すると、それらは同一であり、期待される値が含まれています。

計算シェーダーを間違って実行し、スレッドを生成しすぎているのではないでしょうか? 渡す番号がDispatch間違っていますか?これは 1 次元のデータ行であるため、 を使用するのが理にかなっています[numthreads(64,1,1)]。32 ~ 1024 のさまざまな値を試しました。64 は、AMD GPU を効率的に使用するために必要な最小値であるため、スイート スポットのようです。ともかく。を呼び出すとDispatch、実行するように要求し(vertex_count / 64) + (vertex_count % 64 != 0) ? 1 : 0ます。100,000 個の頂点の場合、呼び出しは になりDispatch(1563,1,1)ます。

ID3D11ShaderResourceView * srvs[] = {bone_array_srv, bone_offset_srv,
                                     bone_count_srv, bone_weights_srv,
                                     cs_vertices_srv};
ID3D11UnorderedAccessView * uavs[] = {cs_output_uav};
UINT srv_count = sizeof(srvs) / sizeof(srvs[0]);
UINT uav_count = sizeof(uavs) / sizeof(uavs[0]);
UINT thread_group_count = vertex_count / 64 + (vertex_count % 64 != 0) ? 1 : 0;

context->CSSetShader(cs, nullptr, 0);
context->CSSetShaderResources(0, srv_count, srvs);
context->CSSetUnorderedAccessViews(0, uav_count, uavs);
context->Dispatch(thread_group_count, 1, 1);

これが頂点シェーダーの実行方法です。

ID3D11ShaderResourceView * srvs[] = {bone_array_srv, bone_offset_srv,
                                     bone_count_srv, bone_weights_srv};
UINT srv_count = sizeof(srvs) / sizeof(srvs[0]);
UINT stride = 0;
UINT offset = 0;

context->GSSetShader(streamout_gs, nullptr, 0);
context->VSSetShader(vs, nullptr, 0);
context->VSSetShaderResources(0, srv_count, srvs);
context->SOSetTargets(1, &vs_output_buf, &offset);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
context->IASetInputLayout(vs_input_layout);
context->IASetVertexBuffers(0, 1, &vs_vertices, &stride, &offset);
context->Draw(vertex_count, 0);

それとも、シェーダー リソース ビューからの読み取りと順序付けされていないアクセス ビューへの書き込みは、頂点バッファーからの読み取りとストリーム出力バッファーへの書き込みよりもはるかに遅いというだけでしょうか?

4

1 に答える 1

5

コンピューティング シェーダーの操作方法を学習しているだけなので、専門家ではありません。骨の計算に関しては、CS は少なくとも VS と同じくらい速く動作するはずです。直感的にnumthreads (64,1,1) は、 のようなものよりも効率が悪いことがわかりますnumthreads (16,16,1)。したがって、このアプローチを試すことができます。

  1. x と y のサイズが同じで、線形バッファーを 2 次レイアウトのように扱います。
  2. x/y サイズを次のように計算します。size = ceil (sqrt (numvertices))
  3. ch(size / 16, size / 16)プログラムとnumthreads (16,16,1)hlsl ファイルで dispatを使用する
  4. sizeおよびnumvertices値をコピーする定数バッファーを割り当てます
  5. インデックスとして使用する代わりにid.x、独自の(線形)インデックスを として計算しますint index = id.y * size +id.x)(おそらく id.xy もインデックスとして可能です)
  6. ほとんどの場合、size * sizeは より大きいためnumvertices、最終的には頂点よりも多くのスレッドになります。hlsl 関数に条件を追加することで、これらの余分なスレッドをブロックできます。

    int index = id.y * size +id.x;
    if (index < numvertices) { .. // your code follows
    

このアプローチにより、CS 計算が高速化されることを願っています。

================編集==================

私の提案は、私自身のタイミング テストに基づいていました。私のケースを検証するために、numthreads パラメータの分散を増やしてこれらのテストを繰り返しました。1034 x 827 = 855,118 ピクセルのマンデルブロ セットを計算します。結果は次のとおりです。

numthreads       Dispatch      groups  threads/  total
  x   y    fps     x     y             group     threads

  4   4    240    259   207    53445     16     855118
  8   8    550    129   103    13361     64     855118
 16  16    600     65    52     3340    256     855118
 32  32    580     32    26      835   1024     855118
 64   1    550     16   827    13361     64     855118
256   1    460      4   827     3340    256     855118
512   1    370      2   827     1670    512     855118

ご覧のとおり、スイート スポットである numthreads(16,16,1) は、numthreads(256,1,1) と同じ数のスレッド グループ (3340) を作成しますが、パフォーマンスは 30% 向上しています。合計スレッド数は常に同じであることに注意してください。私の GPU は ATI 7790 です。

================ 編集 2 ==================

CS と VS の速度に関するあなたの質問をより深く調査するために、非常に興味深いチャンネル 9 のビデオ (PDC09 プレゼンテーション、Microsoft のチーフ アーキテクト Chas Boyd によるダイレクト コンピューティングについてのプレゼンテーション、以下のリンクを参照) を再確認しました。このプレゼンテーションで Boyd は、スレッド レイアウト (numthreads) を最適化すると、スループットが 2 倍になる可能性があると述べています。

しかし、もっと興味深いのは、彼のプレゼンテーションの一部 (40 分目から) で、UAV と GPU メモリ レイアウトの相関関係について説明しています (「グラフィックスとコンピューティング I/O」)。Boyds の発言から間違った結論を引き出したくはありませんが、 UAV を介してバインドされた Compute シェーダーのメモリ帯域幅が他の GPU シェーダーよりも低い可能性は少なくともあります。これが本当なら、たとえば (少なくともバージョン 11.0 では) UAV を VS にバインドできないという事実についての説明があるかもしれません。

これらのメモリ アクセス パターンはハードウェア設計にも依存するため、質問は直接 ATI / NVIDIA エンジニアにエスカレートする必要があります。

結論

私は CS の使用に関する多くの情報を吸収しましたが、CS が同じアルゴリズムを VS よりも遅く実行できるという兆候はまったくありませんでした。もしそうなら、ダイレクト コンピューティングを使用するすべての人にとって重要なことを検出したことになります。

リンク: http://channel9.msdn.com/Events/PDC/PDC09/P09-16

于 2013-11-27T14:02:46.313 に答える