Я хотел отказаться от использования буфера счетчика для некоторых подпрограмм вычислительного шейдера, и у меня возникло непредвиденное поведение на картах 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?Или что-то не так с первым подходом?