OpenGL Compute Shader — правильное использование барьера памяти

Я надеюсь, что смогу читать и записывать потенциально те же элементы SSBO как часть гибкого симулятора с использованием вычислительных шейдеров, но у меня проблемы с синхронизацией. У меня есть тестовый шейдер, который запускается 16 раз, с тремя вариантами ниже, которые, надеюсь, показывают, что я пытаюсь сделать.

layout  (std430, binding=8) coherent buffer Debug
{
  int debug[ ];
};

shared int sharedInt;

layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in;

void main()
{
    ///////     1.     ///////
    sharedInt = debug[0];
    memoryBarrierShared();
    barrier();
    debug[0] = sharedInt[0] + 1;
    memoryBarrierShared();
    barrier();

    // Print debug[0]: 1


    ///////     2.     ///////
    atomicAdd(debug[0], 1);

    // Print debug[0]: 16


    ///////     3.     ///////
    sharedInt = debug[0];
    memoryBarrierShared();
    barrier();
    atomicExchange(debug[0], debug[0]+1);
    memoryBarrierShared();
    barrier();

    // Print debug[0]: 1
}

* Просто для ясности, я запускаю только один из вариантов за раз.

Результат, который я пытаюсь получить для всех из них, заключается в том, что значение debug[0] равно 16, хотя мне нужно использовать что-то вроде 1-го или 3-го варианта в моем моделировании, так как мне нужно читать и записывать в SSBO в та же нить.

Я не уверен, что понимаю роль общей переменной, и, насколько я понимаю, memoryBarrierShared() должен сделать чтение и запись sharedInt видимым для каждого потока в рабочей группе, хотя, если я это сделаю, будет только одна работа Группа отправлена, это тот же результат.

Спасибо за любую помощь.


person Ewan    schedule 12.08.2016    source источник
comment
Не совсем понятно, что вы пытаетесь сделать, почему существует общая переменная или для чего предназначены ваши барьеры. Вы говорите, что хотите, чтобы debug[0] было 16, но на самом деле неясно, почему № 2 не является приемлемым решением этой проблемы.   -  person Nicol Bolas    schedule 12.08.2016
comment
Извини за это. Я не могу использовать № 2, так как мне нужно прочитать из отладки [0], а затем добавить к нему 1. В симуляции у меня есть ssbo, который представляет собой трехмерную сетку или ячейки, в которых хранятся частицы, которые в настоящее время являются каждой ячейкой. Для каждой ячейки я сохраняю количество частиц и индексы частиц в ячейке. При заполнении сетки для каждой частицы выполняется вычислительный шейдер, который находит ячейку, в которой она находится, затем, в зависимости от количества частиц в ячейке, добавляется в соответствующий слот памяти, поэтому мне нужно прочитать количество частиц, а затем увеличить его.   -  person Ewan    schedule 17.08.2016


Ответы (1)


суть в том, что эти дополнения в вариантах 1 и 3 не являются частью атомарной операции. сначала вы читаете из общей переменной /ssbo, затем выполняете сложение, затем пишете. если все вызовы считывают одно и то же значение, все они имеют одинаковый результат сложения и записывают одно и то же значение.

чтобы сделать сложение частью атомарной операции, вы используете atomicAdd, как и в варианте 2.

вот ваш код, аннотированный некоторыми пояснениями:

///////     1.     ///////

// all invocations read debug[0] into the shared variable (presumably 0)
sharedInt = debug[0];

// syncing. but since all wrote the same value into the SSBO, they all would read the same value from it,
// since values written in one invocation are always visible in the same invocation.
memoryBarrierShared();
barrier();

// all invocations do the addition and add 1 to that shared variable (but not write to the shared variable)
// then they all write the result of the addition (1) to the SSBO
debug[0] = sharedInt[0] + 1;

// another syncing that does not help if the shader ends here.
memoryBarrierShared();
barrier();

// since they all write 1, there is no other output possible than a 1 in the SSBO.
// Print debug[0]: 1


///////     2.     ///////
// all invocations tell the "atomic memory unit" (whatever that is exactly)
// to atomicly add 1 to the SSBO.
// that unit will now, sixteen times, read the value that is in the SSBO,
// add 1, and write it back. and because it is does so atomicly,
// these additions "just work" and don't use old values or the like,
// so you have a 16 in your SSBO.
atomicAdd(debug[0], 1);

// Print debug[0]: 16


///////     3.     ///////

// as above, but this has even less effect since you don't read from sharedInt :)
sharedInt = debug[0];
memoryBarrierShared();
barrier();

// all invocations read from debug[0], reading 0.
they all add 1 to the read value, so they now have 1 in their registers.
// now they tell the "atomic memory unit" to exchange whatever there is in
// debug[0] with a 1. so you write a 1 sixteen times into debug[0] and end up with a 1.
atomicExchange(debug[0], debug[0]+1);
memoryBarrierShared();
barrier();

// Print debug[0]: 1
person karyon    schedule 13.08.2016