I’m trying to cache the output of a shader as a PNG on disk so I can use it in the future as a straight-up texture and not require the shader to run. My project does a lot of procedural generation and caching the textures for hundreds of different instances will save gobbles of memory and gpu perf. However, getting the texture data from a RenderTexture onto the CPU and then saved onto disk is very slow. It is well documented that ReadPixels is slow, but the recommended alternative, AsyncGPUReadback, is actually slower for me.
My general algorithm is as follows (full code below):
- Set a ComputeBuffer on my shader that contains the necessary data
- RenderTexture.GetTemporary
- Blit
Then either:
4) texture.ReadPixels
Or
4) AsyncGPUReadback.Request → ImageConversion.EncodeNativeArrayToPNG
- File.WriteAllBytes
When I benchmark this with a 1024x1024 image, ReadPixels takes 40-60ms, Writing takes 30ms, Blit only takes 1ms. If I use AsyncGPUReadback, .Request takes 50ms, ConvertToPNG 30ms, and writing only 9ms.
Is there a faster way to do this? This is just running on my PC, but eventually I want to support more platforms.
ComputeBuffer cornersBuffer = new ComputeBuffer(points.Length, Marshal.SizeOf<Vector2>());
cornersBuffer.SetData(points);
mat.SetBuffer(Shader.PropertyToID("_Corners"), cornersBuffer);
RenderTexture renderTexture = RenderTexture.GetTemporary(res, res, 0, RenderTextureFormat.ARGB32);
Graphics.SetRenderTarget(renderTexture);
GL.Clear(true, true, Color.black);
Graphics.Blit(null, renderTexture, mat);
Graphics.SetRenderTarget(null);
CoalescingTimer.Coalesce(st, "Blit");
RenderTexture.active = renderTexture;
Texture2D texture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGBA32, false);
if (method == 0) {
texture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
texture.Apply();
RenderTexture.ReleaseTemporary(renderTexture);
TextureStorageManager.WriteTexture(texture, indexEntry);
GetComponent<Renderer>().sharedMaterial.mainTexture = texture;
} else if (method == 1) {
StartCoroutine(
CaptureAndSave(renderTexture, (NativeArray<byte> bytesNA) => {
bytesNA = ImageConversion.EncodeNativeArrayToPNG(bytesNA, texture.graphicsFormat, (uint)res, (uint)res);
byte[] bytes = bytesNA.ToArray();
RenderTexture.ReleaseTemporary(renderTexture);
TextureStorageManager.WriteTextureBytes(bytes, indexEntry);
texture.LoadImage(bytes);
GetComponent<Renderer>().sharedMaterial.mainTexture = texture;
st.Stop();
}));
}