DirectX 11 - 使用 AVX 的 AoS 到 SoA 转换导致重新映射时顶点缓冲区损坏

DirectX 11 - AoS to SoA conversion using AVX causing corrupt vertex buffer at remapping

嗨!
我正在 DirectX 11 中实现粒子系统并使用 Intel AVX instrinsics 更新粒子数据以及从 SoA 转换它(数组结构)到 AoS(结构数组),然后再传递到 IA 阶段。

似乎当我在重新映射阶段使用 AVX intrisincs 时,它会导致我的顶点缓冲区(包含粒子顶点)损坏并导致崩溃!

我以 SoA 方式构建了我的粒子数据:

float*      mXPosition;
float*      mYPosition;
float*      mZPosition;

我为每个组件分配内存

mXPosition = (float*) _aligned_malloc( NUM_PARTICLES * sizeof(float), 32 );
mYPosition = (float*) _aligned_malloc( NUM_PARTICLES * sizeof(float), 32 );
mZPosition = (float*) _aligned_malloc( NUM_PARTICLES * sizeof(float), 32 );

我使用 D3D11_USAGE_DYNAMICD3D11_CPU_ACCESS_WRITE 创建顶点缓冲区,以便能够修改 CPU.

上的粒子数据
D3D11_BUFFER_DESC desc;
ZeroMemory( &desc, sizeof( desc ) );

desc.BindFlags              = D3D11_BIND_VERTEX_BUFFER;
desc.Usage                  = D3D11_USAGE_DYNAMIC;
desc.ByteWidth              = sizeof(ParticleVertex12) * NUM_PARTICLES;
desc.StructureByteStride    = sizeof(ParticleVertex12);
desc.CPUAccessFlags         = D3D11_CPU_ACCESS_WRITE;

//Allocating aligned memory for array used for maping vertices to buffer
mVertices = (float*) _aligned_malloc( ( NUM_PARTICLES * 3 ) * sizeof(float), 32 );


if( FAILED( device->CreateBuffer( &desc, &subData, &mVertexBuffer ) ) )
    return E_FAIL;

顶点缓冲区创建成功。

重映射阶段

D3D11_MAPPED_SUBRESOURCE mappedResource;
HRESULT hr = deviceContext->Map( mVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource );

if( SUCCEEDED( hr ) )
{
    size_t counter  = 0;
    for (int baseIndex = 0; baseIndex < NUM_PARTICLES / 8; baseIndex++)
    {
        //   Mapping from SOA-pattern to AOS-pattern 

        //Load
        __m256 xReg = _mm256_load_ps( &mXPosition[baseIndex * 8] );
        __m256 yReg = _mm256_load_ps( &mYPosition[baseIndex * 8] );
        __m256 zReg = _mm256_load_ps( &mZPosition[baseIndex * 8] );

        //Set test values
        xReg = _mm256_set_ps( 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f );
        yReg = _mm256_set_ps( 21.0f, 22.0f, 23.0f, 24.0f, 25.0f, 26.0f, 27.0f, 28.0f );
        zReg = _mm256_set_ps( 31.0f, 32.0f, 33.0f, 34.0f, 35.0f, 36.0f, 37.0f, 38.0f );

        //Shuffle
        __m256 xyReg = _mm256_shuffle_ps( xReg, yReg, _MM_SHUFFLE( 2,0,2,0 ) );
        __m256 yzReg = _mm256_shuffle_ps( yReg, zReg, _MM_SHUFFLE( 3,1,3,1 ) );
        __m256 zxReg = _mm256_shuffle_ps( zReg, xReg, _MM_SHUFFLE( 3,1,2,0 ) );

        __m256 reg03 = _mm256_shuffle_ps( xyReg, zxReg, _MM_SHUFFLE( 2, 0, 2, 0 ) );
        __m256 reg14 = _mm256_shuffle_ps( yzReg, xyReg, _MM_SHUFFLE( 3, 1, 2, 0 ) );
        __m256 reg25 = _mm256_shuffle_ps( zxReg, yzReg, _MM_SHUFFLE( 3, 1, 3, 1 ) );


        //Map, xyz
        __m128* vertexRegAOS = (__m128*)mTempPtr;

        vertexRegAOS[0] = _mm256_castps256_ps128( reg03 );  // x8,y8,z8,x7
        vertexRegAOS[1] = _mm256_castps256_ps128( reg14 );  // y7,z7,x6,y6
        vertexRegAOS[2] = _mm256_castps256_ps128( reg25 );  // z6,x5,y5,z5

        vertexRegAOS[3] = _mm256_extractf128_ps( reg03, 1 );    // x4,y4,z4,x3
        vertexRegAOS[4] = _mm256_extractf128_ps( reg14, 1 );    // y3,z3,x2,y2
        vertexRegAOS[5] = _mm256_extractf128_ps( reg25, 1 );    // z2,x1,y1,z1

        for ( int index = 0, subIndex = 0 ; index < 6; index++ )
        {
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
        }



    memcpy( mappedResource.pData, mVertices, sizeof( ParticleVertex12 ) * NUM_PARTICLES );
    deviceContext->Unmap( mVertexBuffer, 0 );
}

应用程序在遇到此行时崩溃

deviceContext->Unmap( mVertexBuffer, 0 );

并显示消息

D3D11 CORRUPTION: ID3D11DeviceContext::Unmap: First parameter is corrupt or NULL. [ MISCELLANEOUS CORRUPTION #13: CORRUPTED_PARAMETER1]

我可能已经找到了问题所在,但由于我对使用 AVX 还很陌生,所以我还没有设法解决它。

如果我注释掉这部分:

        //Map, xyz
        __m128* vertexRegAOS = (__m128*)mTempPtr;

        vertexRegAOS[0] = _mm256_castps256_ps128( reg03 );  // x8,y8,z8,x7
        vertexRegAOS[1] = _mm256_castps256_ps128( reg14 );  // y7,z7,x6,y6
        vertexRegAOS[2] = _mm256_castps256_ps128( reg25 );  // z6,x5,y5,z5

        vertexRegAOS[3] = _mm256_extractf128_ps( reg03, 1 );    // x4,y4,z4,x3
        vertexRegAOS[4] = _mm256_extractf128_ps( reg14, 1 );    // y3,z3,x2,y2
        vertexRegAOS[5] = _mm256_extractf128_ps( reg25, 1 );    // z2,x1,y1,z1

        for ( int index = 0, subIndex = 0 ; index < 6; index++ )
        {
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
            mVertices[counter++] = vertexRegAOS[index].m128_f32[(subIndex++) % 4];
        }

那么它不会崩溃。类型转换中使用的 mTempPtr 定义为

mTempPtr = new float[6];

有没有 AVX 专家知道我哪里做错了? 感谢您提出任何建议!

谢谢!

我认为您的错误是为六个 32 位浮点数分配 space,然后存储六个 128 位浮点数向量。你很可能。踩着记账数据进行下一次分配,导致尝试free().

时出错
mTempPtr = new float[6];
__m128* vertexRegAOS = (__m128*)mTempPtr;
vertexRegAOS[0] = _mm_setzero_ps();
vertexRegAOS[1] = _mm_setzero_ps();  // buffer overrun here: you only had room for 2 more floats, but you store 4.
vertexRegAOS[2] = ...;  // step on more stuff
... // corrupt even more memory :P

您可以通过使用 VPERM2F128 然后使用单个 256b 存储而不是 2x VEXTRACTF128 来保存一两个微指令(这显然不能微融合其存储和存储数据uops).

    vertexRegAOS[0] = _mm256_castps256_ps128( reg03 );  // x8,y8,z8,x7
    vertexRegAOS[1] = _mm256_castps256_ps128( reg14 );  // y7,z7,x6,y6
    vertexRegAOS[2] = _mm256_castps256_ps128( reg25 );  // z6,x5,y5,z5

    vertexRegAOS[3] = _mm256_extractf128_ps( reg03, 1 );    // x4,y4,z4,x3
    // vertexRegAOS[4] = _mm256_extractf128_ps( reg14, 1 );    // y3,z3,x2,y2
    // vertexRegAOS[5] = _mm256_extractf128_ps( reg25, 1 );    // z2,x1,y1,z1
    __m256 reg45 = _mm256_permute2f128_ps (reg14, reg25, 1|(3<<4) );
    _mm256_storeu_ps( (float*)(vertexRegAOS + 4), reg45);

不过,如果您的代码必须在 AMD Piledriver 上正常运行,请不要使用 256b 存储。它有一个糟糕的性能错误,使 256b 存储比两个 128b 慢得多。

另外,从vertexRegAOS复制到mVertices[counter++]的循环不就是一个memcpy吗?我不明白你为什么不直接存储到它,如果需要的话,使用未对齐的存储。它没有评论,也许我没有花足够的时间盯着它看,如果它实际上没有按顺序复制每个浮点数的话。