Search Unity

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

Discussion in 'Shaders' started by LazloBonin, Nov 25, 2019.

  1. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    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:

    Code (csharp):
    1.  
    2.         ComputeBuffer.CopyCount(trianglesBuffer, triangleCountBuffer, 0);
    3.  
    4.         int[] triangleCountArray = { 0 };
    5.         triangleCountBuffer.GetData(triangleCountArray); // First GetData
    6.         int triangleCount = triangleCountArray[0];
    7.  
    8.         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.
     
    FM-Productions and LooperVFX like this.
  2. xesiomterim

    xesiomterim

    Joined:
    Aug 6, 2018
    Posts:
    3
    Hello :)

    Did you managed to get the buffer count asynchronously? I've got the same problem and I can't find information anywhere else :/
     
  3. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    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:

    Code (CSharp):
    1.  
    2.     private ComputeBuffer triangleCountBuffer;
    3.     private ComputeBuffer trianglesBuffer;
    4.     private int triangleCount;
    5.     private GpuTriangle[] triangles;
    6.     private bool triangleCountAvailable;
    7.     private bool trianglesAvailable;
    8.     private int triangleCountRequestFrame;
    9.     private int trianglesRequestFrame;
    10.  
    11.     void LateUpdate()
    12.     {
    13.         Request();
    14.         Operate();
    15.     }
    16.  
    17.     void Request()
    18.     {
    19.         // (... etc, prepare your request here)
    20.  
    21.         ComputeBuffer.CopyCount(trianglesBuffer, triangleCountBuffer, 0);
    22.         AsyncGPUReadback.Request(triangleCountBuffer, r1 => OnTriangleCountAvailable(r1, Time.frameCount));
    23.         AsyncGPUReadback.Request(trianglesBuffer, r2 => OnTrianglesAvailable(r2, Time.frameCount));
    24.     }
    25.  
    26.     private void OnTriangleCountAvailable(AsyncGPUReadbackRequest request, int requestFrame)
    27.     {
    28.         if (request.hasError || // Something wrong happened
    29.             !Application.isPlaying) // Callback happened in edit mode afterwards
    30.         {
    31.             triangleCountAvailable = false;
    32.             return;
    33.         }
    34.  
    35.         triangleCount = request.GetData<int>()[0];
    36.         triangleCountAvailable = true;
    37.         triangleCountRequestFrame = requestFrame;
    38.     }
    39.  
    40.     private void OnTrianglesAvailable(AsyncGPUReadbackRequest request, int requestFrame)
    41.     {
    42.         if (request.hasError || // Something wrong happened
    43.             !Application.isPlaying) // Callback happened in edit mode afterwards
    44.         {
    45.             trianglesAvailable = false;
    46.             return;
    47.         }
    48.  
    49.         var data = request.GetData<GpuTriangle>();
    50.  
    51.         trianglesAvailable = data.Length == triangles.Length;
    52.         trianglesRequestFrame = requestFrame;
    53.  
    54.         if (trianglesAvailable)
    55.         {
    56.             data.CopyTo(triangles);
    57.         }
    58.         else
    59.         {
    60.             // Debug.LogWarning("Triangle count mismatch. Grid was likely resized.");
    61.         }
    62.     }
    63.  
    64.     private void Operate()
    65.     {
    66.         if (!triangleCountAvailable || !trianglesAvailable)
    67.         {
    68.             return;
    69.         }
    70.  
    71.         if (triangleCountRequestFrame != trianglesRequestFrame)
    72.         {
    73.             // Debug.LogWarning("Out of sync readbacks, skipping meshification.");
    74.             return;
    75.         }
    76.  
    77.         // (etc... operate on the data here, you know it's in sync!)
    78.     }
     
    FM-Productions and LooperVFX like this.
  4. xesiomterim

    xesiomterim

    Joined:
    Aug 6, 2018
    Posts:
    3
    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