Using CopyTexture from RenderTexture to Texture2D not working !?!?!?

Hi, i am using an nVidia GTX 750 Ti with full DX11/12 support and unity 5.6 to 2017.3 beta6 to try using Graphics.CopyTexture to copy information from a render texture to a normal Texture2D which should be possible as the platform and the GPU seems to be capable of this.

Copying from a Texture2D to a render texture is working great just the opposite is not.

How one suppose to copy a RenderTexture information to a regular Texture2D using Gprahics.CopyTexture… ?

All the textures are created without mip maps.

Same with Texture2DArrays. Copying the Texture2DArray to the render texture array is working fine for me but i am not able to do the opposite.

There are no any errors - it is just the normal textures are purely white instead of having the appropriate color copied from the render texture!

Notes:

  • the render texture has color information because when i save it it is as it should.
  • both render texture and the Texture2D are the same ARGB32 type and the same resolution.

I just noticed that the actual CopyTexture is working. The issue though is that the texture to which the information is copied can not be just saved to an asset. I guess the texture exists in the GPU memory, but it is visible in the inspector with all the colors. Once i attempt to save it, the new saved texture is white.

Same question still - how should one have to properly save a Texture2D when Graphics.CopyTexture was used to copy info to it. I guess it should be done on the CPU side before save it to an asset !?

Thank you. I am aware of this method, the point was to use Graphics.CopyTexture but i am ok now. Managed to work it out !

So what was your solution?

What about using “Graphics.Blit(RenderTexture, Texture2D);”?

That doesn’t work. You always Blit into a RenderTexture. Only the source can be a Texture2D.

@Vagabond could you please share your solution? I’m having the same issue, texture is saved grey but appears correct in the explorer.

If you need to save an image out of a render texture, you must use ReadPixels() to copy the contents of a RenderTexture to a Texture2D. This is because the contents of a render texture only exists on the GPU, and CopyTexture() is purely a GPU side operation* if a render texture is used as either the source or destination.

  • If you use CopyTexture() on two non rendertexture texture assets that are both marked as readable, it will also perform a separate CPU side copy in addition to the GPU side copy.
10 Likes

I see. It’s weird because the copy does work, and the inspector does show the Texture2D with what was on the render texture. So it creates a strange scenario where the Texture2D that exists on the GPU is different than the one that exists in the RAM.

What renders on screen is what’s on the GPU, so the inspector is showing you the contents of the texture as they exist on the GPU as that’s all the GPU knows about. Most of the time these are in sync, so it’s not a problem; the texture on the GPU only has data because the GPU that was on disk / in RAM is what was sent to the GPU in the first place. CopyTexture() subverts this expectation as the data in the texture can be copied from essentially any arbitrary data on the GPU, including from render textures which as mentioned above the CPU has zero knowledge of.

That’s what ReadPixels() exists for though, to ask the GPU for the data so it can be transferred back to the domain of the CPU.

Basically CopyTexture is great for doing stuff that only needs to happen on the GPU as it leaves all of the work to the GPU and thus is super fast. That’s what it exists for. For most modern cases the CPU doesn’t ever need to know the contents of a texture. Indeed with the usual work flow the contents of a texture asset don’t even stay in CPU side RAM after it’s been sent to the GPU as it’s no longer needed. Texture assets are read from the disk, sent to the GPU, and then flushed from CPU RAM.

7 Likes

@Vagabond how did you manage to get it worked? I have the same issue and I need your solution. Could you please share?

There is also the option to use the AsyncGPUReadback class. This can avoid any blocking caused by the GPU Resource Readback when leveraging the AsyncCallback and is also more flexible in terms of supported formats. I, for example, needed to copy data from an RG32 RenderTexture which is not supported by ReadPixels. You still need to call SetPixelData which probably isn’t for free but it should be less blocking overall when using the AsyncCallback to set the Texture2D Data. Haven’t done any performance comparisons though.

Edit: Removed unnecessary Graphics.CopyTexture calls (thanks for pointing that out @GeorgeAdamon )

using UnityEngine.Rendering;

//Required Textures (need to have similar formats obviously)
RenderTexture renderTexture = new RenderTexture(64, 64, 0);
Texture2D texture2D = new Texture2D(64, 64);

//Synchronously
 var asyncAction = AsyncGPUReadback.Request(renderTexture, 0);
asyncAction.WaitForCompletion();
texture2D.SetPixelData(asyncAction.GetData<byte>(), 0);
texture2D.Apply();

//Asynchronously
AsyncGPUReadback.Request(renderTexture, 0, (AsyncGPUReadbackRequest asyncAction) =>
{
    texture2D.SetPixelData(asyncAction.GetData<byte>(), 0);
    texture2D.Apply();
});
9 Likes

The above code (at least the asynchronous part, which I tested) works even without the Graphics.CopyTexture() call.

I am still curious on it. If I create a Texture2D like Texture2d test = new Texture2D(), and use CopyTexture to copy a renderTexture to it, what will actually happen? I think creating a texture2D is a pure CPU operation, so the texture2d must be in CPU RAM. But the CopyTexture is doing stuff on GPU side?

My guess is as soon as you call CopyTexture() Unity will create the Texture2D resource on the GPU and copy the contents of the RenderTexture to it.

Did anyone find a fix to use copytexture like extracting only pixel data or
something?

Was any one able to solve this issue for Android as the AsyncReadback request is not supported on android?

The graphic api only enables Vulkan and it should work