Search Unity

Optimizing Texture2D.ReadPixels

Discussion in 'General Graphics' started by karp505, Sep 18, 2015.

  1. karp505

    karp505

    Joined:
    Jul 24, 2014
    Posts:
    18
    Hi!

    I'm running into some performance issues in a pretty sparse test scene. The profiler points to Gfx.ReadbackImage, which is the result of me using Graphics.Blit and then reading that into a Texture2D. Here's some base code:

    Code (CSharp):
    1.         Texture blank = new Texture();
    2.         Material mat = new Material(Shader.Find("Custom/BlitShader"));
    3.         Graphics.Blit(blank, blitTex, mat);
    4.         RenderTexture.active = blitTex;
    5.    
    6.         Texture2D tex = new Texture2D(64, 64, TextureFormat.RGB24, false);
    7.         tex.ReadPixels(new Rect(0, 0, 64, 64), 0, 0);
    8.         tex.Apply();
    9.  
    10.         RenderTexture.active = null;
    11.         color = tex.GetPixel(64 / 2, (int)(64 * .1f));
    So my best understanding is that this will clog the pipeline because you're asking the graphics card to render something (Blit), which it normally does at the end of the pipeline, but then telling it that it must go back after doing that and read in the info (ReadPixels), which it normally does somewhere in the middle of the pipeline. How accurate is that?

    Based on that idea, I tried to use a ping pong buffer, like so:
    Code (CSharp):
    1.         Texture blank = new Texture();
    2.         Material mat = new Material(Shader.Find("Custom/BlitShader"));
    3.         if (pingPong == 0){
    4.             Graphics.Blit(blank, blitTexPing, mat);
    5.             RenderTexture.active = blitTexPong;
    6.         }else if (pingPong == 1){
    7.             Graphics.Blit(blank, blitTexPong, mat);
    8.             RenderTexture.active = blitTexPing;
    9.         }
    10.         Texture2D tex = new Texture2D(64, 64, TextureFormat.RGB24, false);
    11.         tex.ReadPixels(new Rect(0, 0, 64, 64), 0, 0);
    12.         tex.Apply();
    13.  
    14.         RenderTexture.active = null;
    15.         color = tex.GetPixel(64 / 2, (int)(64 * .1f));
    16.  
    17.          if (pingPong == 0)
    18.             pingPong = 1;
    19.         else if (pingPong == 1)
    20.             pingPong = 0;
    I've used a similar ping pong method in other frameworks to great success, but here it hasn't seemed to make any difference. Maybe I'm mis-understanding how Blit and ReadPixels effect the pipeline. Does anybody have any ideas?
     
  2. G-Mika

    G-Mika

    Joined:
    Jul 17, 2012
    Posts:
    99
    My guess is that readpixels is a problem for you because you use it while the gameplay is happening. Readpixels should only be called when performance does not matters, there is no way to optimize this function it will always cost some ms and destroy performances.

    What is on the GPU should stay on the GPU, your best bet is to find a way to make your algorithm GPU only (or maybe try something in native code). I personally tried all the suggestions you can find online (and even more) to optimize this function within Unity, nothing worked.
     
    hippocoder likes this.
  3. karp505

    karp505

    Joined:
    Jul 24, 2014
    Posts:
    18
    Hmm in that case there must be another way to sample a color from a shader. In native OpenGL you can ping pong framebuffers in a way similar to what I did above and read from that with very little performance loss. Surely there's an equivalent in Unity?
     
  4. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Not to my knowledge. I recently spoke to Unity staff and it is also a problem for the ocean middleware Ceto. There is a technique in DX11 that is equivalent but the functionality is missing from Unity to do so, therefore you would have to create a plugin and do it outside of Unity.
     
  5. NavyFish

    NavyFish

    Joined:
    Aug 16, 2013
    Posts:
    28