在Compute Shaders中有没有优雅的方法来处理数组边距? (考虑到你应该在着色器中硬编码工作组的维度)
考虑以下着色器代码,如果使用glDispatchCompute(1,1,1)调用,则计算2048数组的前缀和:
#version 430 core
layout (local_size_x = 1024) in;
layout (binding = 0) coherent readonly buffer block1
{
float input_data[gl_WorkGroupSize.x];
};
layout (binding = 1) coherent writeonly buffer block2
{
float output_data[gl_WorkGroupSize.x];
};
shared float shared_data[gl_WorkGroupSize.x * 2];
void main(void)
{
uint id = gl_LocalInvocationID.x;
uint rd_id;
uint wr_id;
uint mask;
const uint steps = uint(log2(gl_WorkGroupSize.x)) + 1;
uint step = 0;
shared_data[id * 2] = input_data[id * 2];
shared_data[id * 2 + 1] = input_data[id * 2 + 1];
barrier();
for (step = 0; step < steps; step++)
{
mask = (1 << step) - 1;
rd_id = ((id >> step) << (step + 1)) + mask;
wr_id = rd_id + 1 + (id & mask);
shared_data[wr_id] += shared_data[rd_id];
barrier();
}
output_data[id * 2] = shared_data[id * 2];
output_data[id * 2 + 1] = shared_data[id * 2 + 1];
}
但是如果我想为3000个元素的数组计算前缀和呢?
答案 0 :(得分:2)
至于处理额外的,不常用的数据,这很容易:分配更多空间。调度呼叫对整个工作组进行操作。因此,您必须确保为您发送的内容提供足够的存储空间。
只需将其保留为输入缓冲区未初始化,并在读取输出时忽略它。
但是,着色器还存在其他问题,这些问题会阻止他们使用调度调用:
您已明确设计着色器,仅适用于单个工作组调度。也就是说,无论您派遣多少个工作组,他们都将阅读和编写相同的数据。
首先,as previously discussed,停止给缓冲区数据一个绝对长度。您不知道在编译时将调用多少个工作组;这是一个运行时决定。因此,定义数组的大小运行时。
layout (binding = 0) readonly buffer block1
{
float input_data[];
};
layout (binding = 1) writeonly buffer block2
{
float output_data[];
};
另外,请注意缺少coherent
。您不以任何需要限定符的方式使用这些缓冲区。
您的shared
数据仍然需要大小。
其次,每个工作项负责从input_data
读取特定值并将特定值写入output_data
。在当前代码中,此索引为id
,但您当前的代码仅根据工作组中的工作项索引计算它。要为所有工作组中的所有工作项计算它,请执行以下操作:
const uint id = dot(gl_GlobalInvocationID,
vec3(1, gl_NumWorkGroups.x, gl_NumWorkGroups.y * gl_NumWorkGroups.x)
点积只是进行乘法然后对组件求和的一种奇特方式。 gl_GlobalInvocationID
是每个工作项的全局3D位置。每个工作项都有一个唯一的gl_GlobalInvocationId
;点积只是将3D位置转换为一维索引。
第三,在您的实际逻辑中,仅使用gid
来访问缓冲区中的数据。访问共享存储中的数据时,您需要使用gl_LocalInvocationIndex
(基本上是id
以前的数据):
const uint lid = gl_LocalInvocationIndex;
shared_data[lid * 2] = input_data[id * 2];
shared_data[lid * 2 + 1] = input_data[id * 2 + 1];
for (step = 0; step < steps; step++)
{
mask = (1 << step) - 1;
rd_id = ((lid >> step) << (step + 1)) + mask;
wr_id = rd_id + 1 + (lid & mask);
shared_data[wr_id] += shared_data[rd_id];
barrier();
}
output_data[id * 2] = shared_data[lid * 2];
output_data[id * 2 + 1] = shared_data[lid * 2 + 1];
最好使用gl_LocalInvocationIndex
而不是gl_LocalInvocationID.x
,因为有一天你可能需要工作组中的工作项多于本地大小的一个维度。使用gl_LocalInvocationIndex
时,索引将始终考虑本地大小的所有维度。