swifttexturesmetalmetalkitmtktextureloader

MTKTextureLoader causing banding in grayscale image


I'm trying to implement a simple LUT color grade in a metal shader. It works with a color LUT, but when the LUT is grayscale, problems crop up. First, loading the grayscale image causes an "image decoding failed" error, which is fixed with this bug workaround.

By recharacterizing the image as a texture in the asset bundle, it loads successfully, but there's banding on the output image. Sure enough, capturing a GPU frame shows that banding has been introduced in the texture:

banding in LUT texture

This banding doesn't appear when doing a Quick Look in the asset bundle, or on the source PNG. Inspecting the texture's pixel format shows that it's been encoded as ASTC_4x4_sRGB, which Apple documentation states is a compressed format for low-dynamic range content. It seems as though this compression may be responsible for degrading the LUT texture. Normally when working with LUTs, I take care to avoid any compression, but I can't find a way to disable compression or force a pixel format in MTKTextureLoader.

I've also tried various MTKTextureLoader options, including enabling/disabling sRGB, mipmaps, etc.

Any ideas on how to fix the banding?


Solution

  • It's important to understand that when using MTKTextureLoader with texture assets in an asset catalog, most runtime texture loader options are ignored. This may not be documented, but it is currently the case.

    You may be able to avoid this automatic compression (which is well-intentioned but both clumsy and too aggressive) by selecting your asset in the Xcode asset catalog editor and setting its Pixel Format explicitly to something like "8 Bit Normalized - RGBA", which maps to .rgba8Unorm at runtime.

    Xcode texture asset settings