Search Unity

Upload and write to RenderTexture as Texture2DArray in Compute shader !?

Discussion in 'Shaders' started by Vagabond_, Sep 13, 2017.

  1. Vagabond_

    Vagabond_

    Joined:
    Aug 26, 2014
    Posts:
    1,148
    Hi, i need to create a render texture and set it as a Tex2DArrayto a compute shader and then work on every texture in the array. How one can access a specific index of the array in Compute Shader... let's say i would just like to apply a red color to the first texture, green to the second and blue to the third... !?

    Code (CSharp):
    1.  
    2. rendTex = new RenderTexture(1024, 1024, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default);
    3. rendTex.dimension = UnityEngine.Rendering.TextureDimension.Tex2DArray;
    4. rendTex.enableRandomWrite = true;
    5. rendTex.Create();
    6. rendTex.volumeDepth = 3; // NOT SURE - IS THIS THE TOTAL TEXTURE ELEMENTS IN THE ARRAY !?
    7. shader.SetTexture(kernel, "rendTex", rendTex);
    Code (CSharp):
    1.  
    2. // Than in the Compute Shader there is a UAV resources:
    3. RWTexture2D<float4> rendTex : register(u0);
    4.  
    5. [numthreads(8, 8, 1)]
    6. void CSMain(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID)
    7. {
    8.      // SO THE QUESTION IS HOW TO REFERENCE FOR EXAMPLE THE 2ND ELEMENT IN THE ARRAY
    9.      // WITH SPECIFIC X,Y COORDINATES...
    10. }
     
  2. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    357
    It's the Z value in its UV. You'll need to use the correct Texture2DArray type and sample it with SampleLevel().
     
  3. Vagabond_

    Vagabond_

    Joined:
    Aug 26, 2014
    Posts:
    1,148
    Hi, thanks for the feedback. I actually need to apply color ( i see now that i wasn't clear enough sorry ) to all the elements from withing the shader kernel... for example if having 3 texture elements in the array would like to make the first texture in array red color, second green and the third blue color...

    may be something like :
    rendTex[int3(DTid.xy, 0)] = float4(1.0, 0.0, 0.0, 1.0);
    rendTex[int3(DTid.xy, 1)] = float4(0.0, 1.0, 0.0, 1.0);
    rendTex[int3(DTid.xy, 2)] = float4(0.0, 0.0, 1.0, 1.0);

    Is this suppose to work !?

    Thanks !
     
  4. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    357
  5. Vagabond_

    Vagabond_

    Joined:
    Aug 26, 2014
    Posts:
    1,148
    Thanks, glad you have found time to answer again... !

    This code is rather simple but i can not make it work.
    I guess i am missing something... !?
    It just should fill the three textures with a color only... !

    If you can take a look at the code and see if there is something wrong will be awesome !?


    Code (CSharp):
    1.  
    2. RenderTexture rendTexArray;
    3.  
    4. void DoSomeWork()
    5. {
    6.     // Create a render texture array
    7.     int kernel = shader.FindKernel("PaintTexturesInArray");
    8.     rendTexArray = new RenderTexture(512, 512, 32, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);
    9.     rendTexArray.dimension = UnityEngine.Rendering.TextureDimension.Tex2DArray;
    10.     rendTexArray.enableRandomWrite = true;
    11.     rendTexArray.volumeDepth = 3;
    12.     rendTexArray.Create();    
    13.     shader.SetTexture(kernel, "rendTexArray", rendTexArray);
    14.  
    15.     // Dispatch the shader kernel
    16.     shader.Dispatch(kernel, 512 / 8, 512 / 8, 1);
    17.    
    18.     //  Create an array of Texture2Ds and copy the render texture array to it
    19.     Texture2DArray textures2DArray = new Texture2DArray(512, 512, 3, TextureFormat.ARGB32, false);
    20.     Graphics.CopyTexture(rendTexArray, textures2DArray);
    21.     textures2DArray.Apply();
    22.    
    23.     // Last step
    24.     // Use textures2DArray.GetPixels(textureIndex) to get and save every texture in the array
    25.     // All textures are white
    26. }
    27.  


    Code (CSharp):
    1.  
    2. // The Compute Shader
    3.  
    4. #pragma kernel PaintTexturesInArray
    5.  
    6. RWTexture2DArray<float4>    rendTexArray    : register(u0); // UAV
    7.  
    8. #define blocksize 8
    9. [numthreads(blocksize, blocksize, 1)]
    10. void PaintTexturesInArray(uint3 DTid : SV_DispatchThreadID)
    11. {
    12.     // Paint every slice in the array with different color
    13.     rendTexArray[uint3(DTid.x, DTid.y, 0)] = float4(1, 0, 0, 1);
    14.     rendTexArray[uint3(DTid.x, DTid.y, 1)] = float4(0, 1, 0, 1);
    15.     rendTexArray[uint3(DTid.x, DTid.y, 2)] = float4(0, 0, 1, 1);
    16. }
     
  6. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    357
    When you copy using Graphics.CopyTexture you're not actually copying anything on the CPU side - it's a GPU to GPU process. GetPixels won't have access to the data. You'll need to copy your texture to the CPU before trying to access it using any of the GetPixel methods.

    Your shader code looks fine, however. :)
     
  7. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    118
    Apologies for digging this thread up, but there is very little info on this subject elsewhere and this thread has been most helpful on the topic for a compute shader beginner such as myself.

    How would one read back the RWTexture2DArray from the GPU into a Texture2DArray in a c# script?
     
  8. grizzly

    grizzly

    Joined:
    Dec 5, 2012
    Posts:
    357
    Readback from the GPU will cause a pipeline stall which is expensive so generally considered something to avoid (the probable reason why there's little info out there).

    Since the original thread however, Unity has introduced AsyncGPUReadback which allows asynchronous data transfer and thus avoids the stall.
    Code (CSharp):
    1.  // Member fields
    2. public int Width = 512, Height = 512, Depth = 3;
    3. public Texture2DArray Destination;
    4.  
    5. // Create GPU texture array
    6. var source = new RenderTexture(Width, Height, 0, RenderTextureFormat.ARGB32)
    7. {
    8.     dimension = TextureDimension.Tex2DArray,
    9.     enableRandomWrite = true,
    10.     volumeDepth = Depth
    11. };
    12.  
    13. source.Create();
    14.  
    15. //# Write to array. (Dispatch compute shader code goes here)
    16.  
    17. // Create a request and pass in a method to capture the callback
    18. AsyncGPUReadback.Request(source, 0, 0, Width, 0, Height, 0, source.volumeDepth, new Action<AsyncGPUReadbackRequest>
    19. (
    20.     (AsyncGPUReadbackRequest request) =>
    21.     {
    22.         if (!request.hasError)
    23.         {
    24.             // Create CPU texture array
    25.             Destination = new Texture2DArray(Width, Height, request.layerCount, TextureFormat.ARGB32, false);
    26.  
    27.             // Copy the data
    28.             for (var i = 0; i < request.layerCount; i++)
    29.             {
    30.                 Destination.SetPixels32(request.GetData<Color32>(i).ToArray(), i);
    31.             }
    32.  
    33.             Destination.Apply();
    34.        
    35.             // You'll want to release the no longer required GPU texture somewhere here
    36.             // source.Release();
    37.         }
    38.     }
    39. ));
     
    Last edited: Jun 3, 2019
  9. owen_proto

    owen_proto

    Joined:
    Mar 18, 2018
    Posts:
    118
    Thanks so much for the example!
     
    grizzly likes this.
  10. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    Unfortunately, AsyncGPUReadback does not work on GLES. Are there plans to add support for it?
     
  11. viruseg

    viruseg

    Joined:
    Jul 8, 2017
    Posts:
    23
    You can copy the texture using GPU if it supports hardware
    Code (CSharp):
    1. if (SystemInfo.copyTextureSupport == CopyTextureSupport.None)
    2. {
    3.     destination.SetPixels(tex.GetPixels(0), index, 0);
    4. }
    5. else
    6. {
    7.     Graphics.CopyTexture(tex, 0, destination, index);
    8. }
     
    Last edited: Jan 26, 2020