任何优雅的方式都可以处理OpenGL Compute Shaders中的数组边距?

时间:2016-05-05 13:01:03

标签: opengl compute-shader prefix-sum

在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个元素的数组计算前缀和呢?

1 个答案:

答案 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时,索引将始终考虑本地大小的所有维度。