using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using JetBrains.Annotations; using Needle.Engine.Gltf.Experimental; using Needle.Engine.Gltf.Experimental.progressive; using Needle.Engine.Utils; using UnityEngine; using UnityGLTF; using Object = UnityEngine.Object; namespace Needle.Engine.Gltf { [UsedImplicitly] public class TextureCompressionHandler : GltfExtensionHandlerBase, ITextureExportHandler { private static NeedleCompressionSettings defaultCompressionSettings; public override void OnBeforeExport(GltfExportContext context) { base.OnBeforeExport(context); TextureExportHandlerRegistry.Register(this); if (!defaultCompressionSettings) defaultCompressionSettings = Object.FindAnyObjectByType(); } public bool OnTextureExport(GltfExportContext context, ref TextureExportSettings textureSettings, string textureSlot, List extensions) { var texture = textureSettings.Texture; if (!texture) { return false; } if (texture is RenderTexture rt) { extensions.Add(new NEEDLE_progressive_texture_settings(rt.GetId(), -1, false)); return true; } if (texture.IsHDR()) { return false; } var lm = LightmapSettings.lightmaps; foreach (var lightmap in lm) { if (texture == lightmap.lightmapColor) { return false; } } // First check if we have a use progressive textures component on the exported asset var compressionSettings = defaultCompressionSettings; if (context.Root.TryGetComponent(out NeedleCompressionSettings rootSettings)) { compressionSettings = rootSettings; } // Set the texture max size for UnityGltf if (compressionSettings) textureSettings.MaxSize = Mathf.Min(textureSettings.MaxSize, compressionSettings.TextureMaxSize); var ext = CreateCompressionSettings(context, compressionSettings, ref textureSettings, textureSlot); if (ext != null) extensions.Add(ext); if (texture is Texture2D tex2d && compressionSettings != null) { // Texture LODs are disabled: if (compressionSettings.GenerateTextureLODs == false) { // Workaround to disable progressive textures: setting an invalid max size to -1 extensions.Add(new NEEDLE_progressive_texture_settings(tex2d.GetId(), -1, false)); } else { // We only need to load it progressively if the texture is larger than the max size if (compressionSettings.LODsMaxSize < Mathf.Max(tex2d.width, tex2d.height)) { extensions.Add(new NEEDLE_progressive_texture_settings(tex2d.GetId(), compressionSettings.LODsMaxSize)); } } } return true; } private static NEEDLE_compression_texture CreateCompressionSettings(IExportContext context, NeedleCompressionSettings compressionSettings, ref TextureExportSettings exportSettings, string textureSlot) { #if UNITY_EDITOR if (compressionSettings && compressionSettings.DefaultTextureFormat == TextureCompressionMode.None) { return new NEEDLE_compression_texture(TextureCompressionMode.None.Serialize(), textureSlot); } #if !UNITY_6000_0_OR_NEWER // Get importer settings. This feature is discontinued in Unity 6 if (NeedleAssetSettingsProvider.TryGetTextureSettings(exportSettings.Texture, out var needleSettings) && needleSettings.Override) { var compressionMode = needleSettings.CompressionMode.Serialize(); switch (needleSettings.CompressionMode) { case TextureCompressionMode.Automatic: case TextureCompressionMode.Product: case TextureCompressionMode.World: compressionMode = GetCompressionModeFromSlot(compressionSettings, exportSettings, textureSlot); break; } if (compressionMode != null) { var quality = needleSettings.CompressionQualitySupported ? (needleSettings.CompressionQuality / 100f) : -1; var settings = new NEEDLE_compression_texture(compressionMode, textureSlot, quality); settings.maxSize = needleSettings.MaxSize; exportSettings.MaxSize = needleSettings.MaxSize; return settings; } } #endif var texturePath = UnityEditor.AssetDatabase.GetAssetPath(exportSettings.Texture); if (!string.IsNullOrEmpty(texturePath)) { // check asset tags var tags = UnityEditor.AssetDatabase.GetLabels(exportSettings.Texture); if (TryGetCompressionModeFromAssetLabels(tags, out var mode)) return new NEEDLE_compression_texture(mode, textureSlot); if (UnityEditor.EditorUtility.IsPersistent(context.Root)) { var rootTags = UnityEditor.AssetDatabase.GetLabels(context.Root); if (TryGetCompressionModeFromAssetLabels(rootTags, out mode)) return new NEEDLE_compression_texture(mode, textureSlot); } // Export sprite textures using UASTC var importer = UnityEditor.AssetImporter.GetAtPath(texturePath) as UnityEditor.TextureImporter; if (importer && importer.textureType == UnityEditor.TextureImporterType.Sprite) { return new NEEDLE_compression_texture(TextureCompressionMode.UASTC.Serialize(), textureSlot); } } #endif // Auto: we don't insert any extension into the texture here because this should be all determined by the build pipeline // We don't want multiple tools having an opinion here return null; } private static bool TryGetCompressionModeFromAssetLabels(string[] labels, out string mode) { if (labels != null) { foreach (var tag in labels) { if (tag.Equals(TextureCompressionMode.UASTC.Serialize(), StringComparison.OrdinalIgnoreCase)) { mode = TextureCompressionMode.UASTC.Serialize(); return true; } if (tag.Equals(TextureCompressionMode.ETC1S.Serialize(), StringComparison.OrdinalIgnoreCase)) { mode = TextureCompressionMode.ETC1S.Serialize(); return true; } if (tag.Equals(TextureCompressionMode.WebP.Serialize(), StringComparison.OrdinalIgnoreCase)) { mode = TextureCompressionMode.WebP.Serialize(); return true; } } } mode = null; return false; } private static string GetCompressionModeFromSlot(NeedleCompressionSettings settings, TextureExportSettings textureExportSettings, string textureSlot) { switch (textureSlot) { case GLTFSceneExporter.TextureMapType.BaseColor: case GLTFSceneExporter.TextureMapType.Emissive: case GLTFSceneExporter.TextureMapType.Occlusion: case GLTFSceneExporter.TextureMapType.sRGB: return TextureCompressionMode.ETC1S.Serialize(); case GLTFSceneExporter.TextureMapType.Normal: case GLTFSceneExporter.TextureMapType.MetallicRoughness: case GLTFSceneExporter.TextureMapType.Linear: if (settings && settings.DefaultTextureFormat == TextureCompressionMode.Product) return TextureCompressionMode.WebP.Serialize(); return TextureCompressionMode.UASTC.Serialize(); } // if we haven't catched the right slot above, we differentiate by linear setting if (textureExportSettings.Linear) { if (settings && settings.DefaultTextureFormat == TextureCompressionMode.Product) return TextureCompressionMode.WebP.Serialize(); return TextureCompressionMode.UASTC.Serialize(); } return TextureCompressionMode.ETC1S.Serialize(); } } }