[Compute Shaders] How to get append buffer count through AsyncGPUReadback?

I’m implementing a triangulation algorithm in a compute shader that runs at every frame.

My current bottleneck is ComputeShader.GetData because it stalls the CPU until the GPU is done with its queue.

Usually, I can easily get around that with AsyncGPUReadback and reading the data in the callback.

However, for this shader I need to fetch the buffer count (because it’s the number of triangles), and I don’t know how to do that asynchronously.

It seems to imply two GetData calls:

        ComputeBuffer.CopyCount(trianglesBuffer, triangleCountBuffer, 0);

        int[] triangleCountArray = { 0 };
        triangleCountBuffer.GetData(triangleCountArray); // First GetData
        int triangleCount = triangleCountArray[0];

        trianglesBuffer.GetData(triangles, 0, 0, triangleCount); // Second GetData

Is there a way to properly asynchronously “wait” until the shader is done before calling GetData?

Either through AsyncGPUReadback or something else.

2 Likes

Hello :slight_smile:

Did you managed to get the buffer count asynchronously? I’ve got the same problem and I can’t find information anywhere else :confused:

I never found an easy way, but the overall approach I used is this convoluted hack:

  • Use two different AsyncGPUReadbacks, one for the triangle count buffer and the other for the actual triangles
  • Pass the frame number of the current frame when requesting inside the callback delegate, so that you know “which frame” requested each when you receive them
  • Do your final operation on results regularly (for example in LateUpdate), but first check that both buffers returned and both buffers originated from the same request frame, so we know they’re “in sync”

It looks something like this:

    private ComputeBuffer triangleCountBuffer;
    private ComputeBuffer trianglesBuffer;
    private int triangleCount;
    private GpuTriangle[] triangles;
    private bool triangleCountAvailable;
    private bool trianglesAvailable;
    private int triangleCountRequestFrame;
    private int trianglesRequestFrame;

    void LateUpdate()
    {
        Request();
        Operate();
    }

    void Request()
    {
        // (... etc, prepare your request here)

        ComputeBuffer.CopyCount(trianglesBuffer, triangleCountBuffer, 0);
        AsyncGPUReadback.Request(triangleCountBuffer, r1 => OnTriangleCountAvailable(r1, Time.frameCount));
        AsyncGPUReadback.Request(trianglesBuffer, r2 => OnTrianglesAvailable(r2, Time.frameCount));
    }

    private void OnTriangleCountAvailable(AsyncGPUReadbackRequest request, int requestFrame)
    {
        if (request.hasError || // Something wrong happened
            !Application.isPlaying) // Callback happened in edit mode afterwards
        {
            triangleCountAvailable = false;
            return;
        }

        triangleCount = request.GetData<int>()[0];
        triangleCountAvailable = true;
        triangleCountRequestFrame = requestFrame;
    }

    private void OnTrianglesAvailable(AsyncGPUReadbackRequest request, int requestFrame)
    {
        if (request.hasError || // Something wrong happened
            !Application.isPlaying) // Callback happened in edit mode afterwards
        {
            trianglesAvailable = false;
            return;
        }

        var data = request.GetData<GpuTriangle>();

        trianglesAvailable = data.Length == triangles.Length;
        trianglesRequestFrame = requestFrame;

        if (trianglesAvailable)
        {
            data.CopyTo(triangles);
        }
        else
        {
            // Debug.LogWarning("Triangle count mismatch. Grid was likely resized.");
        }
    }

    private void Operate()
    {
        if (!triangleCountAvailable || !trianglesAvailable)
        {
            return;
        }

        if (triangleCountRequestFrame != trianglesRequestFrame)
        {
            // Debug.LogWarning("Out of sync readbacks, skipping meshification.");
            return;
        }

        // (etc... operate on the data here, you know it's in sync!)
    }
2 Likes

Just tried a similar approach today hahaha (I was about to post it when I noticed that AsyncGPUReadback causes memory leaks at least in my case… I’ll try with your approach, who knows xD) but yeah, having two requests seems to be the only workaround right now… anyway, thank you for your time ;D