Многократный вызов InterlockedAdd для RWByteAddressBuffer дает неожиданные результаты (на NVidia) - PullRequest
0 голосов
/ 20 февраля 2019

Я хотел отказаться от использования буфера счетчика для некоторых подпрограмм вычислительного шейдера, и у меня возникло непредвиденное поведение на картах Nvidia

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

Поэтому я хочу выполнить условную запись в несколько мест в буфере (также для упрощения я запускаю только один поток, поскольку поведение также может бытьвоспроизводится таким образом).

Я напишу 4 uints, затем 2 uint3 (используя InterlockedAdd для «симуляции условной записи»)

Поэтому я использую один буфер (с необработанным доступом по uav),со следующей простой компоновкой:

0 -> First counter
4 -> Second counter
8 till 24 -> First 4 ints to write
24 till 48 -> Pair of uint3 to write

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

Я копирую промежуточный ресурс буфера, чтобы проверить значения, так что да, моя конвейерная привязка верна, но я могу опубликовать код, если потребуется.

Теперь, когда я вызываю вычислительный шейдер, выполняя только 4 приращения, как здесь:

RWByteAddressBuffer RWByteBuffer : BACKBUFFER;

#define COUNTER0_LOCATION 0
#define COUNTER1_LOCATION 4
#define PASS1_LOCATION 8
#define PASS2_LOCATION 24

[numthreads(1,1,1)]
void CS(uint3 tid : SV_DispatchThreadID)
{   
    uint i0,i1,i2,i3;
    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i0);
    RWByteBuffer.Store(PASS1_LOCATION + i0 * 4, 10);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i1);
    RWByteBuffer.Store(PASS1_LOCATION + i1 * 4, 20);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i2);
    RWByteBuffer.Store(PASS1_LOCATION + i2 * 4, 30);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i3);
    RWByteBuffer.Store(PASS1_LOCATION + i3 * 4, 40);
}

Затем я получаю следующие результаты (немного отформатированные):

4,0,
10,20,30,40,
12345,12345,12345,12345,12345,12345,12345,12345,12345

Что правильно (счетчик равен 4, как я звонил 4 раза, второй не был вызван), я получаю от 10 до 40 в нужных местах, а остальные имеют значения по умолчанию

Теперь, если я хочу использовать эти индексы, чтобы записать их в другое место:

[numthreads(1,1,1)]
void CS(uint3 tid : SV_DispatchThreadID)
{
    uint i0,i1,i2,i3;
    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i0);
    RWByteBuffer.Store(PASS1_LOCATION + i0 * 4, 10);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i1);
    RWByteBuffer.Store(PASS1_LOCATION + i1 * 4, 20);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i2);
    RWByteBuffer.Store(PASS1_LOCATION + i2 * 4, 30);

    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 1, i3);
    RWByteBuffer.Store(PASS1_LOCATION + i3 * 4, 40);

    uint3 inds = uint3(i0, i1, i2);
    uint3 inds2 = uint3(i1,i2,i3);

    uint writeIndex;
    RWByteBuffer.InterlockedAdd(COUNTER1_LOCATION, 1, writeIndex);
    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex * 12, inds);

    RWByteBuffer.InterlockedAdd(COUNTER1_LOCATION, 1, writeIndex);
    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex * 12, inds2);
}

Теперь, если я запускаю этот код на карте Intel (пробовал HD4000 и HD4600) или на карте ATI 290, я получаю ожидаемые результаты, например:

4,2,
10,20,30,40,
0,1,2,1,2,3

Но при запуске этого на NVidia (используется 970m,gtx1080, gtx570), я получаю следующее:

4,2,
40,12345,12345,12345,
0,0,0,0,0,0

Так что кажется, что оно внезапно возвращает 0 в возвращаемом значении блокированного сложения (оно все равно увеличивается должным образом, так как счетчик равен 4, но в итоге получается 40 вПоследнее значение. Также мы можем SEe что только 0 записано в i1, i2, i3

В случае, если я "зарезервировал память", например, вызов Interlocked только один раз для каждого местоположения (с увеличением на 4 и 2 соответственно):

[numthreads(1,1,1)]
void CSB(uint3 tid : SV_DispatchThreadID)
{
    uint i0;
    RWByteBuffer.InterlockedAdd(COUNTER0_LOCATION, 4, i0);
    uint i1 = i0 + 1;
    uint i2 = i0 + 2;
    uint i3 = i0 + 3;
    RWByteBuffer.Store(PASS1_LOCATION + i0 * 4, 10);
    RWByteBuffer.Store(PASS1_LOCATION + i1 * 4, 20);
    RWByteBuffer.Store(PASS1_LOCATION + i2 * 4, 30);
    RWByteBuffer.Store(PASS1_LOCATION + i3 * 4, 40);

    uint3 inds = uint3(i0, i1, i2);
    uint3 inds2 = uint3(i1,i2,i3);

    uint writeIndex;
    RWByteBuffer.InterlockedAdd(COUNTER1_LOCATION, 2, writeIndex);
    uint writeIndex2 = writeIndex + 1;
    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex * 12, inds);
    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex2 * 12, inds2);
}

Тогда это работает на всех картах, но у меня есть некоторые случаи, когда мне приходится полагаться на более раннее поведение.

В качестве примечания, если я использую структурированные буферы с флагом счетчика на UAV вместорасположение в байтовом адресе и выполните:

RWStructuredBuffer<uint> rwCounterBuffer1;
RWStructuredBuffer<uint> rwCounterBuffer2;

RWByteAddressBuffer RWByteBuffer : BACKBUFFER;

#define PASS1_LOCATION 8
#define PASS2_LOCATION 24

[numthreads(1,1,1)]
void CS(uint3 tid : SV_DispatchThreadID)
{
    uint i0 = rwCounterBuffer1.IncrementCounter();
    uint i1 = rwCounterBuffer1.IncrementCounter();
    uint i2 = rwCounterBuffer1.IncrementCounter();
    uint i3 = rwCounterBuffer1.IncrementCounter();

    RWByteBuffer.Store(PASS1_LOCATION + i0 * 4, 10);
    RWByteBuffer.Store(PASS1_LOCATION + i1 * 4, 20);
    RWByteBuffer.Store(PASS1_LOCATION + i2 * 4, 30);
    RWByteBuffer.Store(PASS1_LOCATION + i3 * 4, 40);

    uint3 inds = uint3(i0, i1, i2);
    uint3 inds2 = uint3(i1,i2,i3);

    uint writeIndex1= rwCounterBuffer2.IncrementCounter();
    uint writeIndex2= rwCounterBuffer2.IncrementCounter();

    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex1* 12, inds);
    RWByteBuffer.Store3(PASS2_LOCATION + writeIndex2* 12, inds2);

}

Это работает корректно на всех картах, но имеет все виды проблем (которые не обсуждаются в этом вопросе).

Это работает на DirectX11 (я не пробовал его на DX12, и это не относится к моему случаю использования, кроме простого любопытства)

Так это ошибка на NVidia?Или что-то не так с первым подходом?

...