I have a snow particle system and I use simplex noise for creating turbulence effect. At first, I made calculations on the CPU, and the simplex noise calls obviously were the bottleneck. The MAX_NUM_PARTICLES limit was 50k (to hold the 60 fps). There was a fatal drop for 75-100k.

That's why I moved the calculation to compute shader like here.

Snow particle system is working, however, I got a weird result: the fps limit left the same, but it does not fall so critically -- I can render 100-200k with 45 fps.

At first, I played with Dispatch params, but the fps was the same for 512, 1, 1 and 1, 1, 1 calls. Second, I convinced that simplex noise calculations were not a problem just passing input coordinates into the output buffer.

Then I commented out different parts of the code and realized that ID3D11DeviceContext:::CopyResource is the bottleneck. I use RWStructuredBuffer buffer to gain compute shader results and, as I know, it requires additional buffer with D3D11_USAGE_STAGING usage and D3D11_CPU_ACCESS_READ flag. So think I can't refuse to use it.

Please share your ideas What can I do about it?

Code part:

    // Create a system memory version of the buffer to read the results back from.
    outputDesc.Usage = D3D11_USAGE_STAGING;
    outputDesc.BindFlags = 0;
    outputDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;

    result = (device->CreateBuffer(&outputDesc, 0, &mRWOutputBuffer));
    if (FAILED(result))
        return false;

    uavDesc.Buffer.FirstElement = 0;
    uavDesc.Buffer.Flags = 0;
    uavDesc.Buffer.NumElements = m_pParticleSystem->GetInstaceCount();
    uavDesc.Format = DXGI_FORMAT_UNKNOWN;
    uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;

    result = device->CreateUnorderedAccessView(mRWBuffer, &uavDesc, &mRWUAV);
    if (FAILED(result))
        return false;

    m_pParticleSystem->FillConstantDataBuffer(deviceContext, mInputBuffer);

    // Enable Compute Shader
    deviceContext->CSSetShader(mComputeShader, nullptr, 0);

    deviceContext->CSSetShaderResources(0, 1, &mInputView);
    deviceContext->CSSetUnorderedAccessViews(0, 1, &mRWUAV, 0);

    // Dispatch
    deviceContext->Dispatch(512, 1, 1);

    // Unbind the input textures from the CS for good housekeeping
    ID3D11ShaderResourceView* nullSRV[] = { NULL };
    deviceContext->CSSetShaderResources(0, 1, nullSRV);

    // Unbind output from compute shader ( we are going to use this output as an input in the next pass, 
    // and a resource cannot be both an output and input at the same time
    ID3D11UnorderedAccessView* nullUAV[] = { NULL };
    deviceContext->CSSetUnorderedAccessViews(0, 1, nullUAV, 0);

    // Disable Compute Shader
    deviceContext->CSSetShader(nullptr, nullptr, 0);

    // Copy result **(FPS DROP HERE)**
    deviceContext->CopyResource(mRWOutputBuffer, mRWBuffer);

Thank you for your answer in advance.

  • \$\begingroup\$ Read-back is almost always slow. It forces the GPU to complete all it's work, and sync with the CPU, which often kills the performance benefits of parallelism in the GPU. You should either do all your particles on the CPU and then render them (only CPU->GPU communication), or do all the particles on the GPU and leave them there. See the AdvancedParticle and ParticlesGS legacy DirectX SDK samples on GitHub. \$\endgroup\$ Mar 29, 2021 at 22:15
  • \$\begingroup\$ Thank you for answering, I will «You should either do all your particles on the CPU and then render them (only CPU->GPU communication» that was my choice before using simplex noise for turbulence effect. \$\endgroup\$ Mar 30, 2021 at 6:09


You must log in to answer this question.

Browse other questions tagged .