ReadPixels was called to read pixels from system frame buffer, while not inside drawing frame. UnityEngine.Texture2D:ReadPixels

greyBow picture greyBow · Oct 5, 2017 · Viewed 8.9k times · Source

I'm trying to create a little test app, using unity. I'm building out one instance of the app and setting it to function as a Host and then running another instance within the unity editor as a client. In this current iteration I have a single button that SHOULD, when pressed is take a screenshot and then send that screenshot to all clients. But nothing seems to to be working. The initial issue that I'm hitting is a ReadPixels error:

ReadPixels was called to read pixels from system frame buffer, while not inside drawing frame. UnityEngine.Texture2D:ReadPixels(Rect, Int32, Int32) UNETChat:OnPostRender() (at Assets/Scripts/UNETChat.cs:50) <TakeSnapshot>c__Iterator0:MoveNext() (at Assets/Scripts/UNETChat.cs:42) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr).

Beyond that, once solved I'm sure that there is a lot of additional work in here that's off the mark. But I'm just starting here. Thanks for the help!

using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.NetworkSystem;

public class MyMsgType
{
    public static short texture = MsgType.Highest + 1;
};

//Create a class that holds a variables to send
public class TextureMessage : MessageBase
{
    public byte[] textureBytes;
    public string message = "Test Received Message"; //Optional
}

public class UNETChat : Chat
{
    NetworkClient myClient;
    public Texture2D previewTexture;
    string messageToSend = "Screen Short Image";

    private void Start()
    {
        //if the client is also the server
        if (NetworkServer.active) 
        {
            // Register to connect event
            myClient.RegisterHandler(MsgType.Connect, OnConnected);
            // Register to texture receive event
            myClient.RegisterHandler(MyMsgType.texture, OnTextureReceive);
        }
        setupClient();
    }

    public IEnumerator TakeSnapshot(int width, int height)
    {
        yield return new WaitForSeconds(0.1F);
        Texture2D texture = new Texture2D(800, 800, TextureFormat.RGB24, true);
        texture.ReadPixels(new Rect(0, 0, 800, 800), 0, 0);
        texture.LoadRawTextureData(texture.GetRawTextureData());
        texture.Apply();

        sendTexture(texture, messageToSend);

        // gameObject.renderer.material.mainTexture = TakeSnapshot;

    }

    public void DoSendTexture()
    {
        StartCoroutine(TakeSnapshot(Screen.width, Screen.height));
    }

    // SERVER //////////////////////////

    //Call to send the Texture and a simple string message
    public void sendTexture(Texture2D texture, string message)
    {
        TextureMessage msg = new TextureMessage();

        //Convert Texture2D to byte array
        msg.textureBytes = texture.GetRawTextureData();
        msg.message = message;

        NetworkServer.SendToAll(MyMsgType.texture, msg);

        Debug.Log("Texture Sent!!");
    }

    // CLIENT //////////////////////////

    // Create a client and connect to the server port
    public void setupClient()
    {
        //Create new client
        myClient = new NetworkClient();
        //Register to connect event
        myClient.RegisterHandler(MsgType.Connect, OnConnected);
        //Register to texture receive event
        myClient.RegisterHandler(MyMsgType.texture, OnTextureReceive);
        //Connect to server
        myClient.Connect("localhost", 4444);
    }

    //Called when texture is received
    public void OnTextureReceive(NetworkMessage netMsg)
    {
        TextureMessage msg = netMsg.ReadMessage<TextureMessage>();

        //Your Received message
        string message = msg.message;
        Debug.Log("Texture Messsage " + message);

        //Your Received Texture2D
        Texture2D receivedtexture = new Texture2D(4, 4);
        receivedtexture.LoadRawTextureData(msg.textureBytes);
        receivedtexture.Apply();
    }

    public void OnConnected(NetworkMessage netMsg)
    {
        Debug.Log("Connected to server");
    }
}

Answer

Programmer picture Programmer · Oct 5, 2017

Wait until the end of the current frame before taking the screenshot or before calling the Texture2D.ReadPixels function. This can be done with the WaitForEndOfFrame class. Notice how I cached it to avoid creating new Object each time the TakeSnapshot function is called.

WaitForEndOfFrame frameEnd = new WaitForEndOfFrame();

public IEnumerator TakeSnapshot(int width, int height)
{
    yield return frameEnd;

    Texture2D texture = new Texture2D(800, 800, TextureFormat.RGB24, true);
    texture.ReadPixels(new Rect(0, 0, 800, 800), 0, 0);
    texture.LoadRawTextureData(texture.GetRawTextureData());
    texture.Apply();
    sendTexture(texture, messageToSend);

    // gameObject.renderer.material.mainTexture = TakeSnapshot;
}

I noticed you have yield return new WaitForSeconds(0.1F) in your script which waits for 0.2 seconds before taking the screenshot. If you must wait for 0.2 seconds, then first wait for 0.2 seconds and then wait for end of frame before taking the screenshot:

WaitForSeconds waitTime = new WaitForSeconds(0.1F);
WaitForEndOfFrame frameEnd = new WaitForEndOfFrame();

public IEnumerator TakeSnapshot(int width, int height)
{
    yield return waitTime;
    yield return frameEnd;

    Texture2D texture = new Texture2D(800, 800, TextureFormat.RGB24, true);
    texture.ReadPixels(new Rect(0, 0, 800, 800), 0, 0);
    texture.LoadRawTextureData(texture.GetRawTextureData());
    texture.Apply();
    sendTexture(texture, messageToSend);

    // gameObject.renderer.material.mainTexture = TakeSnapshot;
}