有什么优雅的方法可以处理计算着色器中的数组边距吗?(考虑到您应该在着色器中对工作组的维度进行硬编码)
考虑以下着色器代码,如果使用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个元素的数组计算前缀和,该怎么办?
至于处理额外的、未使用的数据,这很容易:分配更多的空间。调度呼叫在多个工作组上运行。因此,您必须确保有足够的存储空间来存放您发送的内容。
只需将其保留为输入缓冲区的未初始化状态,并在读取.中的输出时忽略它
但是着色器还有其他问题会阻止它们使用分派调用:
您已将着色器明确设计为仅适用于单个工作组调度。也就是说,无论您调度多少个工作组,它们都将读取和写入相同的数据。
首先,如前所述,停止给缓冲区数据给定绝对长度。您不知道在编译时会调用多少工作组;这是运行时的决定。因此,在运行时定义数组的大小。
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位置变成1D索引。
第三,在实际逻辑中,仅使用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
,索引将始终考虑本地大小的所有维度。