РЕДАКТИРОВАТЬ: я перефразировал вопрос, чтобы сделать его более общим и упростил код.
Я, вероятно, что-то упустил с синхронизацией потоков в вычислительных шейдерах. У меня есть простой вычислительный шейдер, который выполняет параллельное сокращение некоторых чисел, а затем мне нужно изменить итоговую сумму:
#version 430 core
#define SIZE 256
#define CLUSTERS 5
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
struct Cluster {
vec3 cntr;
uint size;
};
coherent restrict layout(std430, binding = 0) buffer destBuffer {
Cluster clusters[CLUSTERS];
};
shared uint sizeCache[SIZE];
void main() {
const ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
const uint id = pos.y * (gl_WorkGroupSize.x + gl_NumWorkGroups.x) + pos.x;
if(id < CLUSTERS) {
clusters[id].size = 0;
}
memoryBarrierShared();
barrier();
sizeCache[gl_LocalInvocationIndex] = 1;
int stepv = (SIZE >> 1);
while(stepv > 0) { //reduction over data in each working group
if (gl_LocalInvocationIndex < stepv) {
sizeCache[gl_LocalInvocationIndex] += sizeCache[gl_LocalInvocationIndex + stepv];
}
memoryBarrierShared();
barrier();
stepv = (stepv >> 1);
}
if (gl_LocalInvocationIndex == 0) {
atomicAdd(clusters[0].size, sizeCache[0]);
}
memoryBarrier();
barrier();
if(id == 0) {
clusters[0].size = 23; //this doesn't do what I would expect
clusters[1].size = 13; //this works
}
}
сокращение работает и дает правильный результат. Если я прокомментирую последнее условие, значение в clusters[0].size
это 262144, что правильно (это количество потоков). Если я раскомментирую это, я ожидаю получить значение 23, потому что, как я понимаю, потоки после barrier()
должны быть синхронизированы и после memoryBarrier()
все предыдущие изменения в памяти должны быть видны. Однако это не работает, это дает результат как 259095. Я предполагаю, что значение 23 переписано предыдущим atomicAdd
из другой ветки, но я не понимаю почему.
Вот как я читаю результат на CPU:
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, resultBuffer);
//currently it dispatches 262144 threads
glDispatchCompute(32, 32, 1);
glCheckError();
glMemoryBarrier(GL_ALL_BARRIER_BITS); //for debug
struct Cl {
glm::vec3 cntr;
uint size;
};
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, resultBuffer);
std::vector<Cl> data(5);
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeOfresult, &data[0]);
У меня есть карта NVIDIA GT630M и Linux с проприетарным драйвером nvidia (331.49).
Вы не можете синхронизировать потоки глобально, то есть через рабочие группы. Об этом говорится в комментариях GuyRT. В вашем коде одна рабочая группа может ударить
clusters[0].size = 23;
в то время как другая рабочая группа счастливо делает атомарные приращения. Поскольку это только первый поток первой рабочей группы, который входит в if(id==0)
блокировать и поскольку большинство графических процессоров распределяют рабочие группы по порядку, тогда значение будет записано один раз, а затем увеличено много раз другими (большинством) рабочих групп.