Search code examples
c#unity-game-enginecameratexture2dkudan

RenderTexture to Texture2D is very slowly


We develop an application on Unity3D which shall take the image from the screen and transfer it to Texture2D. We have difficulties with performance on everage (feeble) devices.
The code is given below. We do a screenshot, then we read pixels (ReadPixels) - here the problem arises, an app runs terribly slow.

We tried to take the texture from the camera, but it doesn’t work because our scanner works in parallel with Kudan, and in turn it blocks access to the camera. Then we tried to limit the area of screen to scan (by taking for scanning a small window, but not full screen) - but there wasn’t visible increase in the performance.
Can anyone help with this?
Here is our code:

IEnumerator DecodeScreen()
 {
   yield return new WaitForEndOfFrame();
   RenderTexture RT = new RenderTexture(Screen.width, Screen.height,24);
   Texture2D screen = new Texture2D(RT.width, RT.height, TextureFormat.RGB24, false, false);
   screen.ReadPixels(new Rect(0, 0, RT.width, RT.height), 0, 0);
   screen.Apply(); 
   Debug.Log(resultText.text);
   GetComponent().targetTexture = null;
   Destroy(RT);
}

Solution

  • There are few ways to improve this code or optional way to convert RenderTexture to Texture2D.

    1.Declare WaitForEndOfFrame as a variable outside the function so that you don't have to do that each time that function is called.

    2.You are creating a new Texture2D each time you call that function. Do that once in the Start function then re-use it. You can freely resize the Texture if RenderTexture width or height changes.

    3.You are also creating new RenderTexture each time you call that function too. It's faster to use RenderTexture.GetTemporary to get temporary RenderTexture. When you are done using it, you can RenderTexture.ReleaseTemporary to release it.

    Texture2D screen = null;
    WaitForEndOfFrame frameEndWait = new WaitForEndOfFrame();
    
    void Start()
    {
        screen = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false, false);
    }
    
    IEnumerator DecodeScreen()
    {
        yield return frameEndWait;
        RenderTexture RT = RenderTexture.GetTemporary(Screen.width, Screen.height, 24);
        //Resize only when Texture2D is null or when its size changes
        if (screen == null || screen.width != RT.width || screen.height != RT.height)
        {
            screen.Resize(RT.width, RT.height, TextureFormat.RGB24, false);
            //screen = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false, false);
        }
        screen.ReadPixels(new Rect(0, 0, RT.width, RT.height), 0, 0);
        screen.Apply();
        RenderTexture.ReleaseTemporary(RT);
    }
    

    Here are other options you have:

    Use Native plugins with C++:

    Send RenderTexture.GetNativeTexturePtr() to a native side of your C++ then create a OpenGL texture from that and load it from C# with pointer using the Texture2D.CreateExternalTexture function and keep updating it with the Texture2D.UpdateExternalTexture function.


    The fastest way to do this is to abandon RenderTexture. Use OpenGL glReadPixels function to take screenshot from C++ side then send the image to Unity with pointer and load it with the Texture2D.CreateExternalTexture function and keep updating it with the Texture2D.UpdateExternalTexture function. You can modify the mouse pixel code from my other post and make it do this.