我正在探索使用计算着色器将骨骼变形应用于网格顶点,而不是使用流输出的顶点着色器。我发现计算着色器的执行速度远低于顶点着色器,但在我把它写下来之前,我想确定我没有做错什么。
对于300个骨骼的100,000个顶点和1,000帧动画数据的测试数据,顶点着色器在大约0.22ms运行,而计算着色器在0.85ms处长达4x。时间是通过D3D API计时器查询(而不是cpu计时器)完成的。
deform_structs.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
的号码错了吗?由于它是一维数据行,因此使用[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);
或者答案只是从着色器资源视图读取并写入无序访问视图比从顶点缓冲区读取并写入流输出缓冲区要慢得多?
答案 0 :(得分:5)
我正在学习如何使用计算着色器,所以我不是专家。关于你的骨骼计算,我确信CS应该至少和VS一样快。 Intuition告诉我numthreads (64,1,1)
效率低于numthreads (16,16,1)
。
所以你可以尝试这种方法:
size = ceil (sqrt (numvertices))
ch(size / 16, size / 16)
,在hlsl文件中使用numthreads (16,16,1)
size
和numvertices
值id.x
作为索引,而是将自己的(线性)索引计算为int index = id.y * size +id.x)
,(也可以将id.xy作为索引)在大多数情况下,size * size
将大于numvertices
,因此您最终会获得比顶点更多的线程。您可以通过在hlsl函数中添加条件来阻止这些额外的线程:
int index = id.y * size +id.x;
if (index < numvertices) { .. // your code follows
我希望这种方法可以加速你的CS计算。
================编辑==================
我的建议是基于我自己的时间测试。为了验证我的情况,我用numthreads参数的更多变化重复了这些测试。 我计算mandelbrot设置超过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)相同的#of线程组(3340),但性能提高了30%。 请注意,总线程数是(并且必须)始终相同! 我的GPU是ATI 7790.
================编辑2 ==================
为了更深入地研究关于CS与VS速度的问题,我重新观看了一个非常有趣的频道9视频(PDC09演示文稿,微软首席架构师Chas Boyd关于直接计算,请参见下面的链接)。在本演示文稿中,Boyd表示优化线程布局(numthreads)可以使吞吐量增加两倍。
然而,更有趣的是他演示的部分(从第40分钟开始),他解释了无人机和GPU内存布局之间的相关性(“图形与计算I / O”)。我不想从Boyds的陈述中得出错误的结论,但似乎至少可能的是,通过无人机绑定的计算着色器做比其他GPU着色器具有更低的内存带宽 。如果这是真的,我们可能会解释无人机无法绑定到VS的事实(例如,至少在版本11.0中)。由于这些内存访问模式也取决于硬件设计,因此您应该直接将问题上报给ATI / NVIDIA工程师。
结论
我已经吸收了大量关于CS使用的信息,但没有丝毫迹象表明CS可以比VS运行相同的算法。如果确实如此,那么您已经检测到对使用直接计算的所有人都很重要的事情。