我有一个
RWTexture2D<float4>
,它由光线生成着色器填充。我需要按一个公共常数值缩放每个像素,该常数只有在光线生成着色器完成后才知道。所以,我正在计算着色器中进行重新缩放。
不幸的是,我对计算着色器不太熟悉。我显然希望缩放操作尽可能快。所以我想我想使用可用的最大并行化。我看到有诸如线程和组之类的东西以及相应的系统值SV_GroupID
,
SV_GroupThreadID
,SV_GroupIndex
和SV_DispatchThreadID
。但我仍然不清楚 [numthreads(THREAD_COUNT_X, THREAD_COUNT_Y, 1)]
和命令列表 Dispatch
调用的最佳选择是什么。为了实现,我尝试了以下方法:
uint const stride_size_x = texture_width / THREAD_COUNT_X,
stride_size_y = texture_height / THREAD_COUNT_Y,
offset_x = thread_id.x * stride_size_x,
offset_y = thread_id.y * stride_size_y;
for (uint v = offset_y; v < offset_y + stride_size_y; ++v)
{
for (uint u = offset_x; u < offset_x + stride_size_x; ++u)
mytexture[uint2(u, v)] *= myscaling;
}
但是,令我惊讶的是,这无法正常工作。图像的一小部分(底部)似乎没有被我的循环捕获。我在这里做错了什么和/或者我应该以不同的方式实现它?备注
:在循环期间,我还将根据 mytexture[uint2(u, v)]
编写
(u, v)
到另一个纹理的变换。所以,如果这很重要,我想在这里做的不仅仅是重新调整。numthreads
解析为 32,否则解析为 64(在 NVidia 上效率稍低,但几乎不会产生影响)。您可以使用
SV_DispatchThreadId
轻松将线程索引转换为像素坐标。然后您在着色器中要做的就是实际缩放。[numthreads(8, 8, 1)] // 8 * 8 = 64
void main(uint3 id : SV_DispatchThreadID)
{
mytexture[id.xy] *= myscaling;
}
在上面的内核中,每个组将产生 8x8 线程。在 NVidia 上,这意味着一半的线程将按顺序执行。每个组将在纹理中的二次 8x8 像素图块上运行。您当然可以将其更改为例如2x16 或 1x32。这不会影响性能,在这种情况下,最好使用较小的二次平铺大小,因为更容易使纹理尺寸为 8 的倍数。如果纹理尺寸不是 8 的倍数,则可能需要添加检查和仅当您在范围内时才应用缩放。纹理外部的有效线程将无操作:
[numthreads(8, 8, 1)]
void main(uint3 id : SV_DispatchThreadID)
{
uint2 dimensions;
result.GetDimensions(dimensions.x, dimensions.y);
if (id.x < dimensions.x && id.y < dimensions.y)
mytexture[id.xy] *= myscaling;
}
语义值
SV_DispatchThreadID
指的是线程在整个调度内的3D索引,因此
xy
部分可以直接映射到像素位置。 docs包含有关如何派生的更多信息。 对于调度大小,您必须提供要生成的组(不是线程)的数量:
commandList->Dispatch(texture->GetDesc().Width / 8, texture->GetDesc().Height / 8, 1);