[SOLVED]ComputeBuffer and AsyncGPUReadbackRequest

Hello everyone.

I’m trying to findout what is going wrong with my code. The thing i want to do is set texture to ComputeShader,
save all pixels to array of struct:

struct MapDataStruct
{
    int x;
    int y;
    float a;
};

and here is whole code of compute shader:

struct MapDataStruct
{
    int x;
    int y;
    float a;
};

int Width;
RWTexture2D<float4> Input;
RWStructuredBuffer<MapDataStruct> Output;

[numthreads(8, 8, 1)]
void MapToStructs(uint3 id : SV_DispatchThreadID)
{
    int index = Width * id.y + id.x;
    Output[index].x = id.x;
    Output[index].y = id.y;
    Output[index].a = Input[id.xy].a;
}

Next in C# script it looks like this:

 ComputeBuffer result = new ComputeBuffer ( tex.width * tex.height, this.GetMapStructStride ( ), ComputeBufferType.Structured );
            int kernel = this.MapComputationsShader.FindKernel ( "MapToStructs" );
            this.MapComputationsShader.SetInt ( "Width", tex.width );
            this.MapComputationsShader.SetTexture ( kernel, "Input", ghelp.GetMiniMapScript ( ).renderTexture_Fow );
            this.MapComputationsShader.SetBuffer ( kernel, "Output", result );
            this.MapComputationsShader.Dispatch ( kernel, tex.width / 8, tex.height / 8, 1 );

            int dataLength = tex.width * tex.height;
            int oneReadSize = dataLength / 64;

            auroraSurfaceFowMap.MapPixelsData = new List<MapDataStruct> ( );
            for ( int currentLine = 0; currentLine < dataLength / oneReadSize; currentLine++ )
            {
                AsyncGPUReadbackRequest request = AsyncGPUReadback.Request ( result, oneReadSize, oneReadSize * currentLine );
                while ( !request.done )
                {
                    yield return null;
                }
                List<MapDataStruct> part = new List<MapDataStruct> ( request.GetData<MapDataStruct> ( ).Where ( s => s.a < 1f ).ToArray ( ) );
                if ( part.Count > 0 )
                {
                    auroraSurfaceFowMap.MapPixelsData.AddRange ( part );
                    Debug.Log ( $"Added {part.Count} pixels." );

                }
                yield return null;
            }
            result.Dispose ( );

Everything was okay when i was using just AsyncGPUReadback.Request ( result ) - without specifying size and offset parameters, but the issue was that there was about 200ms screen freeze when request.GetData<MapDataStruct> ( ) was invoked. Then i just found out that i can tell how big part of data i want to get from the buffer from AsyncGPUReadback.Request(). In my opinion, this just should work but instead of this im getting always the same amount of pixels from ComputeBuffer Debug.Log ( $"Added {part.Count} pixels." );

Problem solved.

What was wrong.
As first, Request() method requires parameters size and offset in bytes. So if i want to read some amount of pixels which are repsented by structure:

struct MapDataStruct
{
    int x;
    int y;
    float a;
};

Then easly multiply size (amount of pixels) by size in bytes of this structure, can be done like this:

public int GetMapStructStride ( )
    {
        unsafe
        {
            return sizeof ( MapDataStruct );
        }
    }

So after all, instead of:

AsyncGPUReadback.Request ( result, oneReadSize, oneReadSize * currentLine )

SHOULD BE:

Request(result, oneReadSize * dataStride, oneReadSize * currentLine * dataStride)

This is how whole working code looks like:

            #region ================================================================ COMPUTE SHADER STUFF
            int dataStride = this.GetMapStructStride ( );
            ComputeBuffer result = new ComputeBuffer ( tex.width * tex.height, dataStride, ComputeBufferType.Structured );
            int kernel = this.MapComputationsShader.FindKernel ( "MapToStructs" );
            this.MapComputationsShader.SetInt ( "Width", tex.width );
            this.MapComputationsShader.SetTexture ( kernel, "Input", ghelp.GetMiniMapScript ( ).renderTexture_Fow );
            this.MapComputationsShader.SetBuffer ( kernel, "Output", result );
            this.MapComputationsShader.Dispatch ( kernel, tex.width / 8, tex.height / 8, 1 );


            int dataLength = tex.width * tex.height;
            int dataLenghtDivider = 512;
            int oneReadSize = dataLength / dataLenghtDivider;

            auroraSurfaceFowMap.MapPixelsData = new List<MapDataStruct> ( );
            for (int currentLine = 0; currentLine < dataLength / oneReadSize; currentLine++ )
            {
                AsyncGPUReadback.Request(result, oneReadSize * dataStride, oneReadSize * currentLine * dataStride, new Action<AsyncGPUReadbackRequest> ( ( s ) =>
                {
                    auroraSurfaceFowMap.MapPixelsData.AddRange ( s.GetData<MapDataStruct> ( ).Where ( x => x.a < 1f ) );
                } ) );
                yield return null;
            }
            yield return new WaitForSeconds ( 1 );
            result.Release ( );

            #endregion
2 Likes