How to read a certain level of mipmaps from a RenderTexture? And Blit() performance...

I wrote a shader to collect some statistics and saved them to the alpha channel of the output texture.
Now, I need to get an average value of them, so I tried to read the last level of the RenderTexture (which means a 1x1 Texture) in OnRenderImage(RenderTexture src, RenderTexture dest):

RenderTexture tempBuffer = RenderTexture.GetTemporary(src.width, src.height, 0, src.format, RenderTextureReadWrite.Linear);
tempBuffer.useMipMap = true;
mipTex = new Texture2D(1, 1, src.graphicsFormat, TextureCreationFlags.None);
Graphics.Blit(src, tempBuffer, _Material, 0);
Graphics.CopyTexture(tempBuffer, 0, tempBuffer.mipmapCount - 1, mipTex, 0, 0);
Color c = mipTex.GetPixel(0, 0);
Graphics.Blit(tempBuffer, dest, _Material, 1);

However, I got a negative value of c.a in Unity Editor, which is obviously wrong.

Update:

It seems to work on my Hololens 1, but the fps dropped heavily from ~50 to ~25, and the memory usage increased from 300+MB to 800+MB. It’s confusing, I just read a fullscreen texture and return its colors in pass 1 like this:

fixed4 frag (v2f i) : SV_Target
{
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
        fixed4 renderTex = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_MainTex, i.uv);
        return renderTex;
}

How can it be so costly?

Update 2:
It did not work on Hololens. c.a is always 0.804. Besides, if I remove tempBuffer and blit from src to dest twice like this:

Graphics.Blit(src, dest, _Material, 0);
Graphics.Blit(src, dest, _Material, 1);

the fps keeps ~50. I’m extremely confused about this problem…

Hi @zyjisdog

You need to use SAMPLE_TEXTURE2D_LOD if you want to sample a certain mipmap level:

SAMPLE_TEXTURE2D_LOD(textureName, samplerName, coord2, lod)

Should work ok if you got a HLSL shader. And on Unity 2019 you have to use SampleLevel if I remember correctly.

Dear @Olmi

Thanks anyway, but I need to read the mipmap pixels to CPU.

Oh, oops. How did I miss that. Sorry :slight_smile:
Wouldn’t GetPixels work then, I thought it supports reading at specified mip level?

public Color[ ] GetPixels(int x, int y, int blockWidth, int blockHeight, int miplevel);

But you do need to the texture back from GPU first.

And looking at your edit, did you get it working?

Did he generated the mip map first from the rendertexture?

No, finally it did not work. I found a similar issue at https://discussions.unity.com/t/670476 , but Texture.ReadPixels cannot access to mipmaps.

According to the documents of Unity, the mipmap is generated automatically if I set the ‘useMipMap’ flag to True.
I also set ‘autoGenerateMips’ to true to ensure that Unity creates the mipmaps. When debugging, the tempBuffer.mipmapCount is greater than 1. I believe the mipmaps are generated successfully.

Seems I must use ReadPixels() according to Using CopyTexture from RenderTexture to Texture2D not working !?!?!? . But ReadPixels() will read all pixels to texture instead of a level of mipmap, which is very expensive.

Anything change with this?

I struggled with this for a good while before finding a workaround, decided to relay what I did here for posterity. In a roundabout way, Olmi was right!

  • Start with your large render texture, make sure that it has mipmaps enabled (duh).
  • Create a second render texture that is the resolution of your desired mip level; this is what you’ll actually end up running ReadPixels() on!
    - Create a shader/material that does nothing but sample _MainTex at your desired mip level and pipe it back out.
  • Graphics.Blit your first texture into your second texture using your newly made material. This feels like a dirty hack but actually works! ymmv on the performance of this, but for my uses it ran great.
  • ReadPixels on your small texture and enjoy the newfound performance gains.