[Unity] Fast Pixel Reading (Part 2): AsyncGPUReadback
Tags: unity3d, gamedev
Published: 2023-05-30 19:55:54

From my previous post, I felt a bit disappointed with the results.

Although my approach for reading pixels was significantly faster, in the end, the performance hit on the running fps count was approximately the same.

Hence this second post, in which I build on the previous post to create a solution that leads to a **negligible** drop from the baseline fps count for a default empty scene, as compared to the optimized approach from my previous post, which leads to a baseline drop of **22.89%**, all while using the optimized *Compute Shader* from my previous post.

# AsyncGPUReadback
The following implementation is an adaptation of my code from the previous post, being an implementation of the *ReadPixels* method using a *Compute Shader* for optimized performance.

In particular, in my new approach, I use the *AsyncGPUReadback* method, which allows me to read data back from the GPU in a non-blocking way.

```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using System;

public class PixelReader2 : MonoBehaviour
{
    [SerializeField] private RenderTexture inputTexture;
    private Texture2D texture2D;

    [SerializeField] private ComputeShader ReadFirstPixel;
    private ComputeBuffer outputBuffer;
    private int kernelID;

    private void Start()
    {
        texture2D = new Texture2D(inputTexture.width, inputTexture.height, TextureFormat.RGBA32, false);

        // Create the output buffer
        outputBuffer = new ComputeBuffer(1, sizeof(float) * 4);

        // Get the kernel ID of the ReadFirstPixel function
        kernelID = ReadFirstPixel.FindKernel("ReadFirstPixel");

        // Set the input texture and output buffer to the shader
        ReadFirstPixel.SetTexture(kernelID, "inputTexture", inputTexture);
        ReadFirstPixel.SetBuffer(kernelID, "outputBuffer", outputBuffer);
    }

    private void Update() {
        StartCoroutine(ReadPixel());
    }

    private IEnumerator ReadPixel()
    {
        // Dispatch the compute shader
        ReadFirstPixel.Dispatch(kernelID, 1, 1, 1);

        // Request the data from the GPU to the CPU
        AsyncGPUReadbackRequest request = AsyncGPUReadback.Request(outputBuffer);

        // Wait for the request to complete
        while (!request.done)
        {
            yield return null;
        }

        if (request.hasError)
        {
            Debug.Log("GPU readback error detected.");
        }
        else
        {
            // Extract the color components from the output array
            float[] outputArray = request.GetData<float>().ToArray();
            Color color = new Color(outputArray[0], outputArray[1], outputArray[2], outputArray[3]);
        }
    }

    void OnDestroy()
    {
        // Release the output buffer
        outputBuffer.Release();
    }
}

```

## Test Setup
For evaluating performance I use the following script, which logs the average fps count over the first 30 seconds of running the game.

```cs
using UnityEngine;

public class AverageFPSCounter : MonoBehaviour
{
    private float startTime;
    private int frameCount;

    private void Start()
    {
        startTime = Time.time;
        frameCount = 0;
    }

    private void Update()
    {
        frameCount++;
        
        if (Time.time - startTime >= 30f)
        {
            float averageFPS = frameCount / (Time.time - startTime);
            Debug.Log("Average FPS over 10 seconds: " + averageFPS);

            // Optionally, you can stop the script from updating further.
            enabled = false;
        }
    }
}
```

## Performance Evaluation
FPS Baseline Drop from **57.53fps** to
old implementation: **22.89% (44.36fps)**
new implementation: **-0.83% (58.01fps)**