no RequestAsyncReadback available with Graphics.ExecuteCommandBufferAsync?

Hi,
I want to dispatch queries at a compute shader function then get the resulting buffer data for using on c# script, on an async way so I don’t block anything as possible.
However when looking the documentation at either ScriptableRenderContext.ExecuteCommandBufferAsync or Graphics.ExecuteCommandBufferAsync I cannot see any reference to RequestAsyncReadback nor RequestAsyncReadbackIntoNativeArray.
How is the way to retrieve data from a buffer that is going to be processed inside a ExecuteCommandBufferAsync?
Any way to know from cpu side that a command buffer async execution has finished?

Thanks

I would like to know as well.

you can’t combine async compute shader with async readback. they are mutually exclusive. the reason is unknown to me.

there are two approaches:

    • use a non-blocking call to CommandBuffer.RequestAsyncReadbackIntoNativeArray.
  • only the callback is available to discover end of readback

  • conversely, it doesn’t give you a AsyncGPUReadbackRequest to poll the end with AsyncGPUReadbackRequest.done

  • use a non-blocking call to Graphics.ExecuteCommandBuffer that freezes the GPU, because it executes in the graphical pipeline

    • create and store a fence with CommandBuffer.CreateAsyncGraphicsFence
  • use GraphicsFence.passed to poll end of execution.

  • the dumbest form of polling is inside the MonoBehaviour.Update event

  • I think coroutines can be used to poll? I don’t really care

  • if you are multi-threading, the easiest and simplest way to poll is inside UnitySynchronizationContext. however, this requires complete and very deep understanding of TAP

  • use a non-blocking call to Graphics.ExecuteCommandBufferAsync

  • only the blocking call ComputeBuffer.GetData is available for reading

I support both approaches. call SystemInfo.supportsAsyncCompute to branch your code. keep the boolean value in a variable, since this call is expensive.

since my compute shader is extremely expensive and my GPU will freeze anyway because it is using the graphical pipeline, I don’t bother with async readback and I just call ComputeBuffer.GetData in both cases

in my project, I have to dispatch the compute shader from a thread which is not the “main thread”. sorry for the incomplete code, but I have a secrecy agreement.

the UnitySynchronizationContext hack (only for multi-threaded situations!):

put this in some MonoBehaviour:

public class [...] : MonoBehaviour
{
    private void Start()
    {
        // Keep this in a "global" variable for later use
        Ucs = SynchronizationContext.Current;
    }
}

call this inside some thread. Task.Wait is blocking! so if the compute shader is expensive, calling this from the “main thread” would freeze it! this entire hack is unnecessary if calling from the “main thread”, just use MonoBehaviour.Update or Unity coroutines!

new Func<Task>(async () =>
{
    SynchronizationContext.SetSynchronizationContext(Ucs);
    await Task.Yield();

    bool isAsync = SystemInfo.supportsAsyncCompute;

    Assert.IsTrue(SystemInfo.supportsComputeShaders);

    CommandBuffer command = new();
    command.SetExecutionFlags(isAsync ? CommandBufferExecutionFlags.AsyncCompute : CommandBufferExecutionFlags.None);
    [...]
    command.DispatchCompute([...]);

    if (isAsync)
    {
        Assert.IsTrue(SystemInfo.supportsGraphicsFence);

        GraphicsFence fence = command.CreateAsyncGraphicsFence();
        Graphics.ExecuteCommandBufferAsync(command, ComputeQueueType.Background);
        while (!fence.passed)
        {
            await Task.Yield();
        }
    }
    else
    {
        Graphics.ExecuteCommandBuffer(command);
    }

    [...].GetData([...]);

    command.Dispose();
    [...]
})().Wait();

notice I take care to name everything blocking/non-blocking. all the API has “async” slapped into their names, but they are not actually “async” at all, they are all just non-blocking with polling or callback notification. it was very confusing for me in the beginning. moreover, none of the API integrates with any of Microsoft’s official async paradigms.

Wow that was extremely indeep and helpful, thank you so much! I’m specially interested about dispatching on other threads, I’ll give it a try :slight_smile:
In our case we need a lot of background compute calls while playing so I think async command buffer is the best approach (I want to test the “background” priority to see the difference). It’s a pity I cannot apparently execute async command buffer on editor, not sure it’s because of being DX11 or because the editor itself :confused: