我原本打算在某些计算着色器例程中不再使用计数器缓冲区,并且在Nvidia卡上出现了某些意外行为
我做了一个非常简化的示例(这样做没有意义,但这是可以重现我遇到的问题的最小示例)。
因此,我想在缓冲区的多个位置执行有条件的写操作(为简化起见,我也只运行一个线程,因为行为也可以通过这种方式重现)。
我将先编写4个uint,然后再编写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
因此,它似乎在互锁的add的返回值中突然返回0(当计数器为4时,它仍然可以正确递增,但最后一个值是40。 我们还可以看到在i1,i2,i3中只有0被写入
如果我“保留内存”,例如,每个位置仅调用一次互锁(分别增加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上的错误吗? 还是第一种方法有问题?