// SPDX-FileCopyrightText: 2023 Unity Technologies and the KTX for Unity authors // SPDX-License-Identifier: Apache-2.0 using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Experimental.Rendering; #if KTX_VERBOSE using System.Text; using Enum = System.Enum; #endif namespace KtxUnity { /// /// Mask of texture features /// [Flags] enum TextureFeatures { None = 0x0, /// /// Format with 4 channels (RGB+Alpha) /// AlphaChannel = 0x1, /// /// Format supports arbitrary resolutions /// NonPowerOfTwo = 0x2, /// /// Format supports arbitrary resolutions /// NonMultipleOfFour = 0x4, /// /// Non square resolution /// NonSquare = 0x8, /// /// Linear value encoding (not sRGB) /// Linear = 0x10 } struct TranscodeFormatTuple { public GraphicsFormat format; public TranscodeFormat transcodeFormat; public TranscodeFormatTuple(GraphicsFormat format, TranscodeFormat transcodeFormat) { this.format = format; this.transcodeFormat = transcodeFormat; } } struct FormatInfo { public TextureFeatures features; public TranscodeFormatTuple formats; public FormatInfo(TextureFeatures features, GraphicsFormat format, TranscodeFormat transcodeFormat) { this.features = features; this.formats = new TranscodeFormatTuple(format, transcodeFormat); } } static class TranscodeFormatHelper { static bool s_Initialized; static Dictionary s_FormatCache; static List s_AllFormats; static void InitInternal() { s_Initialized = true; s_FormatCache = new Dictionary(); if (s_AllFormats == null) { s_AllFormats = new List(); // Add all formats into the List ordered. First supported match will be used. // This particular order is based on memory size (1st degree) // and a combination of quality and transcode speed (2nd degree) // source // Compressed s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare, GraphicsFormat.RGB_ETC2_SRGB, TranscodeFormat.ETC1_RGB)); s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RGB_ETC_UNorm, TranscodeFormat.ETC1_RGB)); s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare, #if UNITY_2018_3_OR_NEWER GraphicsFormat.RGBA_DXT1_SRGB, #else GraphicsFormat.RGB_DXT1_SRGB, #endif TranscodeFormat.BC1_RGB)); s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, #if UNITY_2018_3_OR_NEWER GraphicsFormat.RGBA_DXT1_UNorm, #else GraphicsFormat.RGB_DXT1_UNorm, #endif TranscodeFormat.BC1_RGB)); s_AllFormats.Add(new FormatInfo( TextureFeatures.NonMultipleOfFour, GraphicsFormat.RGB_PVRTC_4Bpp_SRGB, TranscodeFormat.PVRTC1_4_RGB)); s_AllFormats.Add(new FormatInfo( TextureFeatures.NonMultipleOfFour, GraphicsFormat.RGB_PVRTC_4Bpp_UNorm, TranscodeFormat.PVRTC1_4_RGB)); // Compressed with alpha channel s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare, GraphicsFormat.RGBA_ASTC4X4_SRGB, TranscodeFormat.ASTC_4x4_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RGBA_ASTC4X4_UNorm, TranscodeFormat.ASTC_4x4_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare, GraphicsFormat.RGBA_ETC2_SRGB, TranscodeFormat.ETC2_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RGBA_ETC2_UNorm, TranscodeFormat.ETC2_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare, GraphicsFormat.RGBA_BC7_SRGB, TranscodeFormat.BC7_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RGBA_BC7_UNorm, TranscodeFormat.BC7_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare, GraphicsFormat.RGBA_DXT5_SRGB, TranscodeFormat.BC3_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RGBA_DXT5_UNorm, TranscodeFormat.BC3_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonMultipleOfFour, GraphicsFormat.RGBA_PVRTC_4Bpp_SRGB, TranscodeFormat.PVRTC1_4_RGBA)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.Linear | TextureFeatures.NonMultipleOfFour, GraphicsFormat.RGBA_PVRTC_4Bpp_UNorm, TranscodeFormat.PVRTC1_4_RGBA)); // Uncompressed s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.R5G6B5_UNormPack16, TranscodeFormat.RGB565)); // Uncompressed with alpha channel s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare, GraphicsFormat.R8G8B8A8_SRGB, // Also supports SNorm, UInt, SInt TranscodeFormat.RGBA32)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.R8G8B8A8_UNorm, // Also supports SNorm, UInt, SInt TranscodeFormat.RGBA32)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.R4G4B4A4_UNormPack16, TranscodeFormat.RGBA4444)); // Need to extend TextureFeatures to request single/dual channel texture formats. // Until then, those formats are at the end of the list s_AllFormats.Add(new FormatInfo( TextureFeatures.NonPowerOfTwo | TextureFeatures.NonMultipleOfFour | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.B5G6R5_UNormPack16, TranscodeFormat.BGR565)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.R_EAC_UNorm, // Also supports SNorm TranscodeFormat.ETC2_EAC_R11)); s_AllFormats.Add(new FormatInfo( TextureFeatures.AlphaChannel | TextureFeatures.NonPowerOfTwo | TextureFeatures.NonSquare | TextureFeatures.Linear, GraphicsFormat.RG_EAC_UNorm, // Also supports SNorm TranscodeFormat.ETC2_EAC_RG11)); // GraphicsFormat.RGB_A1_ETC2_SRGB,TranscodeFormat.ETC2_RGBA // Does not work; always transcodes 8-bit alpha // GraphicsFormat.RGBA_ETC2_SRGB,TranscodeFormat.ATC_RGBA // Not sure if this works (maybe compatible) - untested // GraphicsFormat.RGB_ETC_UNorm,ATC_RGB // Not sure if this works (maybe compatible) - untested // Not supported via KTX atm // GraphicsFormat.R_BC4_UNorm,TranscodeFormat.BC4_R // GraphicsFormat.RG_BC5_UNorm,TranscodeFormat.BC5_RG // Supported by BasisU, but no matching Unity GraphicsFormat // GraphicsFormat.?,TranscodeFormat.ATC_RGB // GraphicsFormat.?,TranscodeFormat.ATC_RGBA // GraphicsFormat.?,TranscodeFormat.FXT1_RGB // GraphicsFormat.?,TranscodeFormat.PVRTC2_4_RGB // GraphicsFormat.?,TranscodeFormat.PVRTC2_4_RGBA } } public static void Init() { if (!s_Initialized) { InitInternal(); #if KTX_VERBOSE CheckTextureSupport(); #endif } } internal static TranscodeFormatTuple? GetFormatsForImage( IMetaData meta, ILevelInfo li, bool linear = false ) { var formats = TranscodeFormatHelper.GetPreferredFormat( meta.hasAlpha, li.isPowerOfTwo, li.isMultipleOfFour, li.isSquare, linear ); if (!formats.HasValue && meta.hasAlpha) { // Fall back to transcode RGB-only to RGBA texture formats = TranscodeFormatHelper.GetPreferredFormat( false, li.isPowerOfTwo, li.isMultipleOfFour, li.isSquare, linear ); } return formats; } public static TranscodeFormatTuple? GetPreferredFormat( bool hasAlpha, bool isPowerOfTwo, bool isMultipleOfFour, bool isSquare, bool isLinear = false ) { TextureFeatures features = TextureFeatures.None; if (hasAlpha) { features |= TextureFeatures.AlphaChannel; } if (!isPowerOfTwo) { features |= TextureFeatures.NonPowerOfTwo; } if (!isMultipleOfFour) { features |= TextureFeatures.NonMultipleOfFour; } if (!isSquare) { features |= TextureFeatures.NonSquare; } if (isLinear) { features |= TextureFeatures.Linear; } if (s_FormatCache.TryGetValue(features, out var formatTuple)) { return formatTuple; } else { foreach (var formatInfo in s_AllFormats) { if (!FormatIsMatch(features, formatInfo.features)) { continue; } var supported = IsFormatSupported(formatInfo.formats.format, isLinear); if (supported) { s_FormatCache[features] = formatInfo.formats; return formatInfo.formats; } } #if DEBUG Debug.LogErrorFormat("Could not find transcode texture format! (alpha:{0} Po2:{1} sqr:{2})",hasAlpha,isPowerOfTwo,isSquare); #endif return null; } } /// /// Takes a desired target format and returns a fitting , if one was found. /// /// Desired target texture format /// A fitting , if one was found. False otherwise. internal static TranscodeFormatTuple? GetTranscodeFormats(GraphicsFormat graphicsFormat) { TranscodeFormat? tf; switch (graphicsFormat) { case GraphicsFormat.RGB_ETC_UNorm: case GraphicsFormat.RGB_ETC2_SRGB: case GraphicsFormat.RGB_ETC2_UNorm: tf = TranscodeFormat.ETC1_RGB; break; case GraphicsFormat.RGBA_DXT1_SRGB: case GraphicsFormat.RGBA_DXT1_UNorm: tf = TranscodeFormat.BC1_RGB; break; case GraphicsFormat.RGBA_DXT5_SRGB: case GraphicsFormat.RGBA_DXT5_UNorm: tf = TranscodeFormat.BC3_RGBA; break; case GraphicsFormat.RG_BC5_UNorm: case GraphicsFormat.RG_BC5_SNorm: tf = TranscodeFormat.BC5_RG; break; case GraphicsFormat.R_BC4_UNorm: case GraphicsFormat.R_BC4_SNorm: tf = TranscodeFormat.BC4_R; break; case GraphicsFormat.RGBA_BC7_SRGB: case GraphicsFormat.RGBA_BC7_UNorm: tf = TranscodeFormat.BC7_RGBA; break; // // RGB formats are untested. // case GraphicsFormat.B8G8R8_SInt: // case GraphicsFormat.B8G8R8_SNorm: // case GraphicsFormat.B8G8R8_SRGB: // case GraphicsFormat.B8G8R8_UInt: // case GraphicsFormat.B8G8R8_UNorm: // case GraphicsFormat.R8G8B8_SInt: // case GraphicsFormat.R8G8B8_SNorm: // case GraphicsFormat.R8G8B8_SRGB: // case GraphicsFormat.R8G8B8_UInt: // case GraphicsFormat.R8G8B8_UNorm: // tf = TranscodeFormat.RGBA32; // break; case GraphicsFormat.B8G8R8A8_SInt: case GraphicsFormat.B8G8R8A8_SNorm: case GraphicsFormat.B8G8R8A8_SRGB: case GraphicsFormat.B8G8R8A8_UInt: case GraphicsFormat.B8G8R8A8_UNorm: case GraphicsFormat.R8G8B8A8_SInt: case GraphicsFormat.R8G8B8A8_SNorm: case GraphicsFormat.R8G8B8A8_SRGB: case GraphicsFormat.R8G8B8A8_UInt: case GraphicsFormat.R8G8B8A8_UNorm: tf = TranscodeFormat.RGBA32; break; case GraphicsFormat.B5G6R5_UNormPack16: case GraphicsFormat.R5G6B5_UNormPack16: tf = TranscodeFormat.BGR565; break; case GraphicsFormat.B4G4R4A4_UNormPack16: case GraphicsFormat.R4G4B4A4_UNormPack16: tf = TranscodeFormat.RGBA4444; break; case GraphicsFormat.RGBA_ASTC4X4_SRGB: #if UNITY_2020_2_OR_NEWER case GraphicsFormat.RGBA_ASTC4X4_UFloat: #endif case GraphicsFormat.RGBA_ASTC4X4_UNorm: tf = TranscodeFormat.ASTC_4x4_RGBA; break; case GraphicsFormat.RGBA_ETC2_SRGB: case GraphicsFormat.RGBA_ETC2_UNorm: tf = TranscodeFormat.ETC2_RGBA; break; case GraphicsFormat.RGBA_PVRTC_4Bpp_SRGB: case GraphicsFormat.RGBA_PVRTC_4Bpp_UNorm: tf = TranscodeFormat.PVRTC1_4_RGBA; break; case GraphicsFormat.RGB_PVRTC_4Bpp_SRGB: case GraphicsFormat.RGB_PVRTC_4Bpp_UNorm: tf = TranscodeFormat.PVRTC1_4_RGB; break; case GraphicsFormat.R_EAC_SNorm: case GraphicsFormat.R_EAC_UNorm: tf = TranscodeFormat.ETC2_EAC_R11; break; case GraphicsFormat.RG_EAC_SNorm: case GraphicsFormat.RG_EAC_UNorm: tf = TranscodeFormat.ETC2_EAC_RG11; break; default: return null; } return new TranscodeFormatTuple(graphicsFormat, tf.Value); } static bool FormatIsMatch(TextureFeatures required, TextureFeatures provided) { return (required & provided) == required; } internal static bool IsFormatSupported(GraphicsFormat graphicsFormat, bool linear = false) { return SystemInfo.IsFormatSupported( graphicsFormat, #if UNITY_2023_2_OR_NEWER linear ? GraphicsFormatUsage.Linear : GraphicsFormatUsage.Sample #else linear ? FormatUsage.Linear : FormatUsage.Sample #endif ); } #if KTX_VERBOSE // ReSharper disable Unity.PerformanceAnalysis static void CheckTextureSupport () { // Dummy call to force logging all supported formats to console GetSupportedTextureFormats(out _); } static void GetSupportedTextureFormats ( out List graphicsFormats ) { graphicsFormats = new List(); var sb = new StringBuilder(); foreach(var formatInfo in s_AllFormats) { var supported = IsFormatSupported(formatInfo.formats.format); if(supported) { graphicsFormats.Add(formatInfo.formats); } sb.AppendFormat("{0} support: {1}\n",formatInfo.formats.format,supported); } Debug.Log(sb.ToString()); sb.Clear(); var allGfxFormats = (GraphicsFormat[]) Enum.GetValues(typeof(GraphicsFormat)); foreach(var format in allGfxFormats) { sb.Append(format).Append(' '); var usages = new[] { #if UNITY_2023_2_OR_NEWER GraphicsFormatUsage.Sample, GraphicsFormatUsage.Blend, GraphicsFormatUsage.GetPixels, GraphicsFormatUsage.Linear, GraphicsFormatUsage.LoadStore, GraphicsFormatUsage.MSAA2x, GraphicsFormatUsage.MSAA4x, GraphicsFormatUsage.MSAA8x, GraphicsFormatUsage.ReadPixels, GraphicsFormatUsage.Render, GraphicsFormatUsage.SetPixels, GraphicsFormatUsage.SetPixels32, GraphicsFormatUsage.Sparse, GraphicsFormatUsage.StencilSampling, #else FormatUsage.Sample, FormatUsage.Blend, FormatUsage.GetPixels, FormatUsage.Linear, FormatUsage.LoadStore, FormatUsage.MSAA2x, FormatUsage.MSAA4x, FormatUsage.MSAA8x, FormatUsage.ReadPixels, FormatUsage.Render, FormatUsage.SetPixels, FormatUsage.SetPixels32, FormatUsage.Sparse, FormatUsage.StencilSampling, #endif }; foreach (var usage in usages) { sb .Append(usage) .Append(':') .Append(SystemInfo.IsFormatSupported(format, usage) ? "1" : "0") .Append(' '); } sb.Append('\n'); } Debug.Log(sb.ToString()); } #endif } }