I have already filed it as a bug report, but I wanted to know if anybody has an idea of how I can workaround it. I wrote this utility to execute Texture2D.Compress() asynchronously and it works, but I get the error 'Assertion failed on expression: ‘mipLevel < m_MipCount’ whenever I call compress. (to recreate it attach this mono-behaviour to an object and press play.)
using System;
using System.Collections;
using Unity.Collections;
using Unity.Profiling;
using UnityEngine;
public class BugExample : MonoBehaviour
{
public Texture2D testUncompressed;
public Texture2D testCompressed;
public int width = 256, height = 256;
// Start is called before the first frame update
void Start()
{
testUncompressed = new Texture2D(width, height, TextureFormat.RGBA32, true);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
testUncompressed.SetPixel(x, y, new Color(x / (float)width, y / (float)height, 0.3f));
testUncompressed.Apply();
(IEnumerator job, Texture2D result) = AsyncCompression.CompressTextureJob(testUncompressed);
while (job.MoveNext()) { }
testCompressed = result;
}
// Update is called once per frame
void Update()
{
}
}
public static class AsyncCompression
{
public static TextureFormat GetCompressedFormat(TextureFormat format)
{
return format switch
{
TextureFormat.RGBA32 => TextureFormat.DXT5,
TextureFormat.ARGB32 => TextureFormat.DXT5,
TextureFormat.RGB24 => TextureFormat.DXT1,
TextureFormat.R8 => TextureFormat.BC4,
_ => throw new NotSupportedException()
};
}
// log2 of value. returns 0 for 1, 1 for 2, 2 for 4
static int PowerOfTwo(int value)
{
for (int i = 0; i < 32; ++i)
if (value >> i == 0)
return i - 1;
throw new ArgumentOutOfRangeException($"cannot get power of two: {value}");
}
public static (IEnumerator job, Texture2D result) CompressTextureJob(Texture2D source, int blockCount = 8)
{
TextureFormat compressed = GetCompressedFormat(source.format);
Texture2D result = new(source.width, source.height, compressed, source.mipmapCount - 2, !source.isDataSRGB, true);
return (CompressTexureJob(source, result, blockCount), result);
}
static readonly ProfilerMarker PCompressTextureBlock = new("Compress Texture Block");
static IEnumerator CompressTexureJob(Texture2D source, Texture2D result, int blockCount)
{
bool linear = !source.isDataSRGB;
NativeArray<byte>[] resultData = new NativeArray<byte>[result.mipmapCount];
for (int i = 0; i < resultData.Length; ++i)
resultData[i] = result.GetPixelData<byte>(i); // pointer, not copy
NativeArray<byte>[] sourceData = new NativeArray<byte>[source.mipmapCount];
for (int i = 0; i < sourceData.Length; ++i)
sourceData[i] = source.GetPixelData<byte>(i); // pointer, not copy
int sourceBlockByteSize = sourceData[0].Length / blockCount;
int blockByteSize = resultData[0].Length / blockCount;
int blockHeight = source.height / blockCount;
int blockMipCount = PowerOfTwo(blockHeight) - 2;
Texture2D block = new(source.width, blockHeight, source.format, blockMipCount, linear, true);
for (int i = 0; i < blockCount; ++i)
{
PCompressTextureBlock.Begin();
for (int mip = 0; mip < blockMipCount; ++mip)
block.SetPixelData(sourceData[mip], mip, (sourceBlockByteSize >> mip * 2) * i);
block.Compress(true);
for (int mip = 0; mip < blockMipCount; ++mip)
{
NativeArray<byte> blockData = block.GetPixelData<byte>(mip);
int mipBlockByteSize = blockByteSize >> mip * 2;
resultData[mip].Slice(mipBlockByteSize * i, mipBlockByteSize).CopyFrom(blockData);
}
block.Reinitialize(block.width, block.height, source.format, true);
PCompressTextureBlock.End();
yield return null;
}
GameObject.Destroy(block);
int remainingMips = source.mipmapCount - 2 - blockMipCount;
if (remainingMips > 0)
{
Texture2D lowMips = new(source.width >> blockMipCount, source.height >> blockMipCount, source.format, remainingMips, linear, true);
for (int mip = 0; mip < remainingMips; ++mip)
lowMips.SetPixelData(sourceData[mip + blockMipCount], mip);
lowMips.Compress(true);
for (int mip = 0; mip < remainingMips; ++mip)
result.SetPixelData(lowMips.GetPixelData<byte>(mip), mip + blockMipCount);
GameObject.Destroy(lowMips);
}
result.Apply(false);
}
}