- Home /
Optimize saving render buffer to file
I have found a lot of questions and answers on this topic, but no complete recommended way to do this. We are rendering HD frames and want to save them to disk in raw format (i.e. not JPEG or PNG) at 60FPS. right now we are getting around 8FPS with the following code:
IEnumerator RecordFrame()
{
yield return new WaitForEndOfFrame();
var renderTexture = new RenderTexture(Screen.width, Screen.height, 32, RenderTextureFormat.ARGB32);
cam.targetTexture = renderTexture;
cam.Render();
cam.targetTexture = null;
RenderTexture.active = renderTexture;
Texture2D screenshot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGBA32, false);
screenshot.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
screenshot.Apply();
RenderTexture.active = null;
String path = "Screenshot_" + m_num + ".bin";
byte[] bytes = screenshot.GetRawTextureData();
File.WriteAllBytes(path, bytes);
m_num++;
}
public void LateUpdate()
{
StartCoroutine(RecordFrame());
}
From past posts it seems that ReadPixels is the culprit, and needs optimization. I am trying glReadPixels but it returns a black frame, like the one in this post: https://answers.unity.com/questions/1251855/glreadpixels-in-unity-5-always-get-black-frame.html
Is there a definitive answer to this question? Or any asset in the store that can achieve this functionality out of the box?
Answer by wok33 · Jul 19, 2018 at 09:51 AM
I ended up using the solution from here: https://github.com/keijiro/AsyncCaptureTest I get ~40 FPS for HD resolution, main bottleneck seems to be the write to disk function which blocks Update().
For future reference, with unity 2018.2:
public class AsyncCapture : MonoBehaviour
{
Queue<AsyncGPUReadbackRequest> _requests = new Queue<AsyncGPUReadbackRequest>();
int m_num = 0;
void Update()
{
while (_requests.Count > 0)
{
var req = _requests.Peek();
if (req.hasError)
{
Debug.Log("GPU readback error detected.");
_requests.Dequeue();
}
else if (req.done)
{
var data = req.GetData<byte>().ToArray(); // request bytes so that they are directly writeable to the file
string filename = "Screenshot_" + m_num + ".raw";
using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create)))
{
writer.Write(data);
}
m_num++;
_requests.Dequeue();
}
else
{
break;
}
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (_requests.Count < 8)
_requests.Enqueue(AsyncGPUReadback.Request(source));
else
Debug.Log("Too many requests.");
Graphics.Blit(source, destination);
}
}
Cool. Thanks for the code example. If you want to save a video to disc, you can also take a look at https://stackoverflow.com/questions/44865782/how-to-record-the-screen-in-unity-and-make-output-as-a-file. There's also lots of tools on the asset store that can help. For example the free https://assetstore.unity.com/packages/tools/video/video-capture-75653 (I haven't tried it, but it looks promising). I think you might run into throughput problems if you try to save 60 uncompressed full frames directly to disc; even if you make it multithreaded (~475 megabytes per second is more than most SSDs can handle).
Your answer
Follow this Question
Related Questions
glreadpixel without canvas 0 Answers
How do I get the raw pixels of a RFloat RenderTexture on the CPU? 5 Answers
Rendering an HD PNG off-screen? 0 Answers
ReadPixels Overlaying Opacity 0 Answers
iOS: Specify Top Level Texture Mipmap 0 Answers