有什么优雅的方法可以处理 OpenGL 计算着色器中的数组边距吗?
Any elegant way deal with array margins in OpenGL 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 个元素的数组的前缀和怎么办?
至于处理额外的、未使用的数据,这很简单:分配更多 space。调度呼叫对工作组的整数倍进行操作。因此,您必须确保有足够的存储空间来存放您发送的内容。
只要将其保留为输入缓冲区未初始化,并在读取输出时忽略它。
但是您的着色器还有其他问题会阻止它们处理调度调用:
您已将着色器明确设计为仅适用于单个工作组分派。也就是说,不管你分派多少个工作组,他们都在读写相同的数据。
首先,,停止为缓冲区数据提供绝对长度。您不知道在编译时将调用多少个工作组;这是一个运行时决定。所以让数组的大小运行时定义。
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
,索引将始终考虑局部大小的所有维度。
有什么优雅的方法可以处理计算着色器中的数组边距吗? (考虑到您应该在着色器中硬编码工作组的维度)
考虑以下着色器代码,如果使用 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 个元素的数组的前缀和怎么办?
至于处理额外的、未使用的数据,这很简单:分配更多 space。调度呼叫对工作组的整数倍进行操作。因此,您必须确保有足够的存储空间来存放您发送的内容。
只要将其保留为输入缓冲区未初始化,并在读取输出时忽略它。
但是您的着色器还有其他问题会阻止它们处理调度调用:
您已将着色器明确设计为仅适用于单个工作组分派。也就是说,不管你分派多少个工作组,他们都在读写相同的数据。
首先,
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
,索引将始终考虑局部大小的所有维度。