Search Unity

Question Efficient way of reading from a RWTexture2D in Compute Shader

Discussion in 'Shaders' started by sstrong, Jul 23, 2020.

  1. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    I'm trying to find an efficient way of reading a RWTexture2D from the GPU to the CPU. I have a relatively small 1024x1024 Render Texture which is assigned to the compute shader.

    On the CPU side when I attempt to read it back with ReadPixels it is really slow.

    Code (CSharp):
    1. Texture2D destTex = new Texture2D(widthInt, heightInt, TextureFormat.ARGB32, false, true);
    2.  
    3. RenderTexture.active = rTex
    4. // Really slow 0.5 seconds
    5. destTex.ReadPixels(new Rect(0, 0, widthInt, heightInt), 0, 0);
    6.  
    7. // process results
    8. Color[] pixels = destTex.GetPixels(0);
    9.  
    10. // do other stuff here
    How can I efficiently read the Texture back from the GPU?
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
  3. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    I did see this though this code is inline with a whole bunch of dependencies and my code has to run multiple times in a loop. I have no problem with stalling the main thread (it is not rendering in real-time so frame rate is not applicable).

    Are there any sample of how this method can be used inside say a for loop?
     
  4. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    Actually it looks like AsyncGPUReadback is slower than buffer.GetData(..) which I'm currently using. I had hoped using a RenderTexture might be faster, but that seems to suffer from an equally slow ReadPixels method.
     
  5. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Yeah if your texture is fairly large the ReadPixels is going to be a little slow, that's one of the unfortunate downsides about transferring data from the GPU to CPU, it's one of the slowest things to do.

    You can speed up your code a bit after the copy though, instead of using GetPixels() you can use the newer
    tex.GetRawTextureData<Color32>();
    which will give you direct access to the texture array data to iterate over.

    In fact, as long as you don't regenerate your Texture2D object, this collection reference will continue to point to the current CPU side texture data. So you can set up this reference on startup, just do your ReadPixels and now you just iterate over that already assigned collection.


    Code (CSharp):
    1. private static NativeArray<Color32> CopyRT(RenderTexture rt, ref Texture2D toTexture)
    2. {
    3.     if(toTexture == null) { toTexture = new Texture2D(rt.width, rt.height); }
    4.     else if(toTexture.width != rt.width || toTexture.height != rt.height) { toTexture.Resize(rt.width, rt.height); }
    5.     //currently assuming you've already set rt as the active
    6.     toTexture.ReadPixels(new Rect(0,0, rt.width, rt.height), 0, 0);
    7.  
    8.     return toTexture.GetRawTextureData<Color32>();
    9. }
    10.  
    11. private static void Example(NativeArray<Color32> nativeTex)
    12. {
    13.     foreach(var col in nativeTex)
    14.     {
    15.         Debug.Log(col);
    16.     }
    17. }
    But a question would be, do you even need this to be on CPU side? You can use Graphics.CopyTexture() to copy data between RTs on the GPU side very quickly instead of bringing it to the CPU side.
     
    Last edited: Jul 23, 2020
  6. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    Yeah, I'm updating terrain detail data. The slowest bit is a very lengthy 0.5 seconds to copy either a buffer or a RT back to the CPU. Would be nice if we could just get Unity to push it to the terrain detail data on the GPU. The GetRawTextureData for NativeArrays could be useful in the future.
     
  7. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    What about the terrainData.CopyActiveRenderTextureToTexture and Heightmap methods?
    Or getting the references to the Textures for the terrain data you want to overwrite and using Graphics.CopyTexture?
     
  8. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    So yes, they are available for splatmaps and heightmaps but not details (grass).
     
  9. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,550
    Is it the texture of the grass objects themselves you want to modify? Or the layout of the grass?
     
  10. sstrong

    sstrong

    Joined:
    Oct 16, 2013
    Posts:
    2,255
    The grass patches. I've had working compute shaders for ages but the biggest bottleneck is now really just reading back the data from the compute shader to the CPU and then updating the terrain detail layers. Copying the data from the GPU is very slow - and I wonder why.

    Happy to take say a 50ms or even 100ms hit per 1K texture but 500ms seems weird.
     
    Last edited: Jul 23, 2020