Search Unity

How to avoid Material.SetBuffer waiting the compute shader to finish?

Discussion in 'Shaders' started by methusalah999, Jan 24, 2020.

  1. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Hi folks,

    I'm using a ComputeShader that produces a result in a ComputeBuffer. Then I give that buffer to a material via SetBuffer(). This last call seems to take forever (~3ms). As I understand things, this is due to the SetBuffer() method waiting for the ComputeShader's kernel being exectuted on the GPU.

    The result of the buffer will only be required when the GPU will execute the mesh rendering. Is there a way to make the SetBuffer instant and desynchronize CPU and GPU in that sitaution?

    Note that both kernel dispatch and SetBuffer() are called from LateUpdate methods, making the waiting even longer.

    I've imagined delaying that call to the next frame, but I'm pretty sure it would create artifacts in my case.

    How do you folks solve that issue?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You can call SetBuffer() on the material before calling Dispatch(). You're not passing the values to the material, you're setting a reference to the buffer which doesn't change after it's initially created.

    However I don't know if that will remove the issue you're having. The problem is Dispatch() is blocking, meaning the c# code won't continue until the compute shader finishes and the CPU gets notification from the GPU that it's finished. I believe you need to call Dispatch from an async command buffer if you want it to not block script execution, though there's no guarantee it'll be finished by the time your material starts to get rendered if you're calling it in LateUpdate and it's taking 3ms to execute.
    https://docs.unity3d.com/ScriptReference/Graphics.ExecuteCommandBufferAsync.html
     
  3. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I can indeed set the buffer on the material once and for all, thanks for this advice. It seems to be 30% faster, I don't really understand why.

    Tell me if i'm wrong, but the computeShader.Dispatch() won't wait until the kernel execution is terminated. The CPU code will only wait the GPU if I perform a GetData on a ComputeBuffer bound to this kernel. I assumed that using SetData() after the dispatch was also blocking CPU, due to a lock system, telling the CPU that this buffer is currently being written.

    My best guess is: using SetData each frame was indeed creating a delay, waiting for the buffer to be written each time. Now that I call SetData on start once and for all, the waiting still exists, but occurs later in the rendering pipeline. Hence the delay to wait is shorter and the performance is better.

    I'll try calling the dispatch asynchronously, but I fear that it would ignore any lock. The shader might read the buffer in the middle of its computation.

    Unity documentation on ComputeBuffer is a little opaque.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    SetData & GetData are transferring data between the CPU and GPU, which is always going to incur some extra cost (especially going the other way with GetData pulling data from the GPU to the CPU). Both are also going to be blocking as you might be calling Dispatch or rendering something that's going to use that data immediately.

    Dispatch() is also blocking, but only for the execution. Again in case you render something immedately afterward that's reliant on that information, the compute shader needs to be done first otherwise it might still be working on that data when you render that thing. That's the danger of using the async version.