Graphics.CopyTexture copies wrong mip on Windows 10 build

Hey folks,

so I have this super simple example code that runs fine on the editor but behaves weird on a build:

// _tex is a 2D Texture with mipmap displayed on left quad
leftQuad.material.mainTexture = _tex;

// Create a texture without a mipmap
var tex = new Texture2D(_tex.width, _tex.height, _tex.graphicsFormat, 0, TextureCreationFlags.None);

// copy full resolution texture (mip 0) and show it on right quad
Graphics.CopyTexture(_tex, 0, 0, tex, 0, 0);
rightQuad.material.mainTexture = tex;

Te result looks like this:


In other words: whenever I try to access _tex.mip[0] it behaves as if it was _tex.mip[1] meaning the mip with only half the width/height. I have tried this in a compute shader, using GetPixels and using Graphics.CopyTextureas above. Always the same result!


I have tested all this using:

  • Unity 2020.2.3f1
  • Unity 2020.2.2f1

Any idea? Help would be highly appreciated.

I just found the reason for this weird behaviour!


Explanation and Workaround 1

Project Settings > Quality > Texture Quality

This setting was Half Res. Setting it to Full Res fixes the issue. But this way you cannot reduce the texture resolution for better performance settings!


**Deeper understanding and Workaround 2 **

What’s weird: The quality setting still does not explain the difference between editor and build. There is also a variable that allows you to check for this seeting and it is 1 in both cases for me:

QualitySettings.masterTextureLimit

This value is 0 for Full Resolution, 1 for Half Resolution etc. In other words: it defines the smallest mip level that is used when rendering textures and it seems to be used by CopyTexture internaly. We can use that to “fix” the issue:

// The scale factor is 2^QualitySettings.masterTextureLimit:
var scaleFactor = 1 << QualitySettings.masterTextureLimit;
var width = _tex.width / scaleFactor;
var height = _tex.width / scaleFactor;
// Create a texture with the reduced size and no mipmap
var tex = new Texture2D(width, height, _tex.graphicsFormat, 0, TextureCreationFlags.None);

// Temporarily set the texture Limit to 0 so taht CopyTexture behaves properly
var texLimit = QualitySettings.masterTextureLimit;
QualitySettings.masterTextureLimit = 0;
Graphics.CopyTexture(_tex, 0, texLimit, tex, 0, 0);
QualitySettings.masterTextureLimit = texLimit;

Compute Shader

The original reason I came accross this was that I got the exact same weird behavior using a very simple compute shader. I was able to fix it using that same workaround. Here is a working version that might help you too:

/// C#
CubemapRenderTexture = new RenderTexture(_tex.width, _tex.height, 0, _tex.graphicsFormat, 0);
{
    CubemapRenderTexture.dimension = TextureDimension.Tex2D;
    CubemapRenderTexture.enableRandomWrite = true;
    CubemapRenderTexture.useMipMap = false;
    CubemapRenderTexture.Create();
}
int kernelIndex = computeShader.FindKernel("CSMain");
computeShader.SetTexture(kernelIndex, "INPUT", _tex);
computeShader.SetTexture(kernelIndex, "OUTPUT", CubemapRenderTexture);


// Run the kernel with textureLimit==0
var texLimit = QualitySettings.masterTextureLimit;
QualitySettings.masterTextureLimit = 0;
computeShader.Dispatch(kernelIndex, _tex.width, _tex.height, 1);
QualitySettings.masterTextureLimit = texLimit;


/// .compute file
#pragma kernel CSMain

// Inputs
Texture2D<float4> INPUT;
RWTexture2D<float4> OUTPUT;

[numthreads(1,1,1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
    OUTPUT[id.xy] = INPUT[id.xy]; // Same as INPUT.mip[0][id.xy] => full resolution texture
}

Wrap up

To me this looks like a bug. However it is really weird that I cannot find much about this online though I only do very basic stuff and this should happen to a lot of people out there. So maybe I missed something essential here?