Async texture import in Unity engine
TLDR: Here’s a repository containing code for async texture import in Unity: https://codeberg.org/matiaslavik/unity-async-textureimport
In Unity, the standard way of importing textures is through the ImageConversion.LoadImage (previously
Texture2D.Load) function. However, this function has some limitations. One of these is the lack of async import. You are forced to use it on the main thread, and it will block execution until file reading, import and mipmap generation is done. This can result in long stalls, which is unacceptable for most games and realtime applications.
Since this part of Unity’s source code is not open sourced, we are forced to re-invent the wheel and do the import ourselves. To do this, we can use the amazing FreeImage library. FreeImage is a library for image import/export and manipulation, written in C.
Since FreeImage is a native library written in C and not C#, we need to create a C# wrapper for it. In Unity this is quite simple. You just need to create a class with
static extern function declarations for all the native FreeImage functions you want to call, and mark them with the
[DllImport(FreeImageLibrary, EntryPoint = "FreeImage_OpenMemory")] public static extern IntPtr FreeImage_OpenMemory(IntPtr data, uint size_in_bytes);
See FreeImage.cs for all FreeImage wrappers used in the project.
We import the texture using the
FreeImage_Load function. This is done in the ImportTextureFromFile function.
// Load from file IntPtr texHandle = FreeImage.FreeImage_Load(format, texturePath, 0);
Next we can use the
FreeImage_ConvertToRawBits function to read the texture data into a byte array:
uint width = FreeImage.FreeImage_GetWidth(texHandle); uint height = FreeImage.FreeImage_GetHeight(texHandle); uint size = width * height * 4; byte data = new byte[size]; FreeImage.FreeImage_ConvertToRawBits(Marshal.UnsafeAddrOfPinnedArrayElement(data, 0), texHandle, (int)width * 4, 32, 0, 0, 0, false
We can then create a new
Texture2D and pass the raw texture data to it using the LoadRawTextureData function. However there is one problem left: We don’t have any mipmaps, and the
LoadRawTextureData function expects you to create the mipmaps on your own (or not use any mipmaps).
First of all, what is a mipmap? A mipmap is simply an image sequence of the same image at different scales. For example, if your image has a dimension of 256x256, then the mipmap will contain the image at the following resolutions: 256x256, 128x128, 64x64, etc. (resolution is recursively divided by 2). This is used for rendering objects at far distances. If you don’t use mipmaps, then high resolution textures will look noisy at far distances.
Again, FreeImage comes to rescue. Using the
FreeImage_Rescale function, we can downscale the image recursively to create a mipnap. This is done in the GenerateMipMaps function.
Finally we can create the
Texture2D instance and pass it the texture data through the
LoadRawTextureData function. This is done in the CreateTexture function.
Texture2D tex = new Texture2D(texData.width, texData.height, TextureFormat.BGRA32, texData.mipLevels, false); tex.filterMode = FilterMode.Trilinear; tex.LoadRawTextureData(texData.data); tex.Apply(false, true);
Note: We need to call Texture2D.Apply with the
updateMipmaps parameter set to
false, since we created the mipmaps ourselves.