From 499ed5f03b62bb372cf2dbd9b2a4217158a4c297 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 6 Sep 2024 17:13:56 +0900 Subject: [PATCH 01/30] feat(trace-and-optimize): initial commit for optimize texture --- Editor/Inspector/TraceAndOptimizeEditor.cs | 3 +++ .../TraceAndOptimize/OptimizeTexture.cs | 15 +++++++++++++++ .../TraceAndOptimize/OptimizeTexture.cs.meta | 3 +++ .../TraceAndOptimizeProcessor.cs | 2 ++ Localization/en-us.po | 3 +++ Localization/ja-jp.po | 3 +++ Runtime/TraceAndOptimize.cs | 6 ++++++ 7 files changed, 35 insertions(+) create mode 100644 Editor/Processors/TraceAndOptimize/OptimizeTexture.cs create mode 100644 Editor/Processors/TraceAndOptimize/OptimizeTexture.cs.meta diff --git a/Editor/Inspector/TraceAndOptimizeEditor.cs b/Editor/Inspector/TraceAndOptimizeEditor.cs index e3adbafa8..60a892b9d 100644 --- a/Editor/Inspector/TraceAndOptimizeEditor.cs +++ b/Editor/Inspector/TraceAndOptimizeEditor.cs @@ -14,6 +14,7 @@ internal class TraceAndOptimizeEditor : AvatarGlobalComponentEditorBase private SerializedProperty _optimizeAnimator = null!; // Initialized in OnEnable private SerializedProperty _mergeSkinnedMesh = null!; // Initialized in OnEnable private SerializedProperty _allowShuffleMaterialSlots = null!; // Initialized in OnEnable + private SerializedProperty _optimizeTexture = null!; // Initialized in OnEnable private SerializedProperty _mmdWorldCompatibility = null!; // Initialized in OnEnable private SerializedProperty _advancedSettings = null!; // Initialized in OnEnable private GUIContent _advancedSettingsLabel = new GUIContent(); @@ -29,6 +30,7 @@ private void OnEnable() _optimizeAnimator = serializedObject.FindProperty(nameof(TraceAndOptimize.optimizeAnimator)); _mergeSkinnedMesh = serializedObject.FindProperty(nameof(TraceAndOptimize.mergeSkinnedMesh)); _allowShuffleMaterialSlots = serializedObject.FindProperty(nameof(TraceAndOptimize.allowShuffleMaterialSlots)); + _optimizeTexture = serializedObject.FindProperty(nameof(TraceAndOptimize.optimizeTexture)); _mmdWorldCompatibility = serializedObject.FindProperty(nameof(TraceAndOptimize.mmdWorldCompatibility)); _advancedSettings = serializedObject.FindProperty(nameof(TraceAndOptimize.advancedSettings)); } @@ -58,6 +60,7 @@ protected override void OnInspectorGUIInner() EditorGUILayout.PropertyField(_allowShuffleMaterialSlots); EditorGUI.indentLevel--; } + EditorGUILayout.PropertyField(_optimizeTexture); _advancedSettingsLabel.text = AAOL10N.Tr("TraceAndOptimize:prop:advancedOptimization"); AdvancedOpened = EditorGUILayout.Foldout(AdvancedOpened, _advancedSettingsLabel); diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs new file mode 100644 index 000000000..3d6b90e10 --- /dev/null +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -0,0 +1,15 @@ +using nadena.dev.ndmf; + +namespace Anatawa12.AvatarOptimizer.Processors.TraceAndOptimizes; + +internal class OptimizeTexture : TraceAndOptimizePass +{ + public override string DisplayName => "T&O: OptimizeTexture"; + + protected override void Execute(BuildContext context, TraceAndOptimizeState state) + { + if (!state.OptimizeTexture) return; + + throw new System.NotImplementedException(); + } +} diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs.meta b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs.meta new file mode 100644 index 000000000..02edb4c45 --- /dev/null +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d0262ac6157e42f2a36488a8b7ba45a1 +timeCreated: 1725609364 \ No newline at end of file diff --git a/Internal/TraceAndOptimizeBase/TraceAndOptimizeProcessor.cs b/Internal/TraceAndOptimizeBase/TraceAndOptimizeProcessor.cs index 87bd8602d..8d65a722f 100644 --- a/Internal/TraceAndOptimizeBase/TraceAndOptimizeProcessor.cs +++ b/Internal/TraceAndOptimizeBase/TraceAndOptimizeProcessor.cs @@ -14,6 +14,7 @@ public class TraceAndOptimizeState public bool OptimizeAnimator; public bool MergeSkinnedMesh; public bool AllowShuffleMaterialSlots; + public bool OptimizeTexture; public bool MmdWorldCompatibility = true; public bool PreserveEndBone; @@ -48,6 +49,7 @@ internal void Initialize(TraceAndOptimize config) OptimizeAnimator = config.optimizeAnimator; MergeSkinnedMesh = config.mergeSkinnedMesh; AllowShuffleMaterialSlots = config.allowShuffleMaterialSlots; + OptimizeTexture = config.optimizeTexture; MmdWorldCompatibility = config.mmdWorldCompatibility; PreserveEndBone = config.preserveEndBone; diff --git a/Localization/en-us.po b/Localization/en-us.po index 9313f2a2e..d979c2663 100644 --- a/Localization/en-us.po +++ b/Localization/en-us.po @@ -621,6 +621,9 @@ msgstr "Optimize Animator" msgid "TraceAndOptimize:prop:mergeSkinnedMesh" msgstr "Merge Skinned Mesh" +msgid "TraceAndOptimize:prop:optimizeTexture" +msgstr "Optimize Texture" + msgid "TraceAndOptimize:prop:allowShuffleMaterialSlots" msgstr "Allow Shuffling Material Slots" diff --git a/Localization/ja-jp.po b/Localization/ja-jp.po index 681d3de7a..04d077559 100644 --- a/Localization/ja-jp.po +++ b/Localization/ja-jp.po @@ -535,6 +535,9 @@ msgstr "スキンメッシュレンダラーを統合する" msgid "TraceAndOptimize:prop:allowShuffleMaterialSlots" msgstr "マテリアルスロットの前後関係を変えることを許可する" +msgid "TraceAndOptimize:prop:optimizeTexture" +msgstr "テクスチャを最適化する" + msgid "TraceAndOptimize:tooltip:allowShuffleMaterialSlots" msgstr "マテリアルスロットの前後関係を変えることでドローコールを減らすことを許可します。これは描画順に影響を与える可能性があります。" diff --git a/Runtime/TraceAndOptimize.cs b/Runtime/TraceAndOptimize.cs index e1849b2e5..3636a2d20 100644 --- a/Runtime/TraceAndOptimize.cs +++ b/Runtime/TraceAndOptimize.cs @@ -79,6 +79,12 @@ internal TraceAndOptimize() [SerializeField] internal bool allowShuffleMaterialSlots; + [NotKeyable] + [AAOLocalized("TraceAndOptimize:prop:optimizeTexture")] + [ToggleLeft] + [SerializeField] + internal bool optimizeTexture = true; + // common parsing configuration [NotKeyable] [AAOLocalized("TraceAndOptimize:prop:mmdWorldCompatibility", From b3ed4660eeebc57153c41e1f9e4f9fefa44856bb Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 6 Sep 2024 21:49:12 +0900 Subject: [PATCH 02/30] feat(shader-knowledge): add ShaderKnowledge for optimize texture --- Editor/ShaderKnowledge.cs | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index a96d97eee..f40b7e120 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; +using UnityEditor; using UnityEngine; namespace Anatawa12.AvatarOptimizer @@ -97,5 +99,124 @@ private static bool CheckAffectedUvDiscard(Material material, MeshInfo2 meshInfo return x == column && y == row; }); } + + public interface IMaterialPropertyAnimationProvider + { + bool IsAnimated(string propertyName); + } + + public enum UVChannel + { + UV0 = 0, + UV1 = 1, + UV2 = 2, + UV3 = 3, + UV4 = 4, + UV5 = 5, + UV6 = 6, + UV7 = 7, + // For example, ScreenSpace (dither) or MatCap + NonMeshRelated = 0x100 + 0, + Unknown = -1, + } + + public class TextureUsageInformation + { + public string MaterialPropertyName { get; } + public UVChannel UVChannel { get; } + + public TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) + { + MaterialPropertyName = materialPropertyName; + UVChannel = uvChannel; + } + } + + // TODO: define return type + /// + /// Returns texture usage information for the material. + /// + /// + /// null if the shader is not supported + public static TextureUsageInformation[]? GetTextureUsageInformationForMaterial(Material material, IMaterialPropertyAnimationProvider animation) + { + if (AssetDatabase.GetAssetPath(material.shader).StartsWith("Packages/jp.lilxyzw.liltoon")) + { + // it looks liltoon! + return GetTextureUsageInformationForMaterialLiltoon(material, animation); + } + + return null; + } + + private static TextureUsageInformation[]? GetTextureUsageInformationForMaterialLiltoon(Material material, IMaterialPropertyAnimationProvider animation) + { + // This implementation is made for my Anon + Wahuku for testing this feature. + // TODO: version check + var information = new List(); + + var uvMain = UVChannel.UV0; + + // TODO: UV Animation, angle (_MainTex_ScrollRotate) , Tilting, Offsets (_ST) for MainTex + information.Add(new TextureUsageInformation("_DitherTex", UVChannel.NonMeshRelated)); + information.Add(new TextureUsageInformation("_MainTex", uvMain)); + information.Add(new TextureUsageInformation("_MainColorAdjustMask", uvMain)); + information.Add(new TextureUsageInformation("_MainGradationTex", uvMain)); + + if (material.GetInt("_UseMain2ndTex") != 0 || animation.IsAnimated("_UseMain2ndTex")) + { + UVChannel main2ndUV; + if (animation.IsAnimated("_Main2ndTex_UVMode")) + { + main2ndUV = UVChannel.Unknown; + } + else + { + switch (material.GetInt("_Main2ndTex_UVMode")) + { + case 0: main2ndUV = UVChannel.UV0; break; + case 1: main2ndUV = UVChannel.UV1; break; + case 2: main2ndUV = UVChannel.UV2; break; + case 3: main2ndUV = UVChannel.UV3; break; + case 4: main2ndUV = UVChannel.NonMeshRelated; break; + default: main2ndUV = UVChannel.Unknown; break; + } + } + information.Add(new TextureUsageInformation("_Main2ndTex", main2ndUV)); + // TODO: UV Animation, angle (_MainTex2_ScrollRotate) , Tilting, Offsets (_ST) for MainTex2 + information.Add(new TextureUsageInformation("_Main2ndBlendMask", main2ndUV)); // NO ScaleOffset + information.Add(new TextureUsageInformation("_Main2ndDissolveMask", main2ndUV)); + information.Add(new TextureUsageInformation("_Main2ndDissolveNoiseMask", main2ndUV)); + // TODO: isDecurl for MainTex2? + } + + // TODO: Main3rd + + // Matcap + if (material.GetInt("_UseMatCap") != 0 || animation.IsAnimated("_UseMatCap")) + { + information.Add(new TextureUsageInformation("_MatCapTex", UVChannel.NonMeshRelated)); + information.Add(new TextureUsageInformation("_MatCapBlendMask", UVChannel.UV0)); // No ScaleOffset + // TODO: more properties like: _MatCapBumpMap + } + + // TODO: Matcap2nd + + // rim light + if (material.GetInt("_UseRim") != 0 || animation.IsAnimated("_UseRim")) + { + information.Add(new TextureUsageInformation("_RimColorTex", uvMain)); + } + + // outline + if (material.GetInt("_UseOutline") != 0 || animation.IsAnimated("_UseOutline")) + { + information.Add(new TextureUsageInformation("_OutlineColorTex", uvMain)); + information.Add(new TextureUsageInformation("_OutlineWidthMask", uvMain)); // ?? + } + + // TODO: Many Properties + return information.ToArray(); + } } } From 84bb23e0ab828ee3c25e0042fd15cb68c6ae826f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 7 Sep 2024 19:57:01 +0900 Subject: [PATCH 03/30] feat(optimize-texture): filter out materials --- Editor/OptimizerPlugin.cs | 1 + .../TraceAndOptimize/OptimizeTexture.cs | 215 +++++++++++++++++- 2 files changed, 215 insertions(+), 1 deletion(-) diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index 769b0fee9..fbdd64962 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -73,6 +73,7 @@ protected override void Configure() .Then.Run(Processors.TraceAndOptimizes.ConfigureRemoveZeroSizedPolygon.Instance) .Then.Run(Processors.MergeBoneProcessor.Instance) .Then.Run(Processors.RemoveZeroSizedPolygonProcessor.Instance) + .Then.Run(Processors.TraceAndOptimizes.OptimizeTexture.Instance) .Then.Run(Processors.AnimatorOptimizer.RemoveInvalidProperties.Instance) ; }); diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 3d6b90e10..d88ebdea6 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -1,4 +1,12 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Anatawa12.AvatarOptimizer.AnimatorParsersV2; +using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; using nadena.dev.ndmf; +using UnityEngine; +using Object = UnityEngine.Object; namespace Anatawa12.AvatarOptimizer.Processors.TraceAndOptimizes; @@ -6,10 +14,215 @@ internal class OptimizeTexture : TraceAndOptimizePass { public override string DisplayName => "T&O: OptimizeTexture"; + readonly struct SubMeshId : IEquatable + { + public readonly MeshInfo2 MeshInfo2; + public readonly int SubMeshIndex; + + public SubMeshId(MeshInfo2 meshInfo2, int subMeshIndex) + { + MeshInfo2 = meshInfo2; + SubMeshIndex = subMeshIndex; + } + + public bool Equals(SubMeshId other) => MeshInfo2 == other.MeshInfo2 && SubMeshIndex == other.SubMeshIndex; + public override bool Equals(object? obj) => obj is SubMeshId other && Equals(other); + public override int GetHashCode() => HashCode.Combine(MeshInfo2, SubMeshIndex); + } + protected override void Execute(BuildContext context, TraceAndOptimizeState state) { if (!state.OptimizeTexture) return; - throw new System.NotImplementedException(); + // those two maps should only hold mergeable materials and submeshes + var materialUsers = new Dictionary>(); + var materialsBySubMesh = new Dictionary>(); + + var unmergeableMaterials = new HashSet(); + + // first, collect all submeshes information + foreach (var renderer in context.GetComponents()) + { + var meshInfo = context.GetMeshInfoFor(renderer); + + if (meshInfo.SubMeshes.All(x => x.SharedMaterials.Length == 1 && x.SharedMaterial != null)) + { + // Good! It's mergeable + for (var submeshIndex = 0; submeshIndex < meshInfo.SubMeshes.Count; submeshIndex++) + { + var subMesh = meshInfo.SubMeshes[submeshIndex]; + + var possibleMaterials = new HashSet(new[] { subMesh.SharedMaterial! }); + var (safeToMerge, animatedMaterials) = GetAnimatedMaterialsForSubMesh(context, + meshInfo.SourceRenderer, submeshIndex); + possibleMaterials.UnionWith(animatedMaterials); + + if (safeToMerge) + { + materialsBySubMesh.Add(new SubMeshId(meshInfo, submeshIndex), possibleMaterials); + foreach (var possibleMaterial in possibleMaterials) + { + if (!materialUsers.TryGetValue(possibleMaterial, out var users)) + materialUsers.Add(possibleMaterial, users = new HashSet()); + + users.Add(new SubMeshId(meshInfo, submeshIndex)); + } + } + else + { + unmergeableMaterials.UnionWith(possibleMaterials); + } + } + } + else + { + // Sorry, I don't support this (for now) + var materialSlotIndex = 0; + + foreach (var subMesh in meshInfo.SubMeshes) + { + foreach (var material in subMesh.SharedMaterials) + { + if (material != null) unmergeableMaterials.Add(material); + + var (_, materials) = GetAnimatedMaterialsForSubMesh(context, renderer, materialSlotIndex); + unmergeableMaterials.UnionWith(materials); + materialSlotIndex++; + } + } + } + } + + // collect usageInformation for each material, and add to unmergeableMaterials if it's impossible + { + var usageInformations = new Dictionary(); + + foreach (var (material, _) in materialUsers) + { + var provider = new MaterialPropertyAnimationProvider( + materialUsers[material].Select(x => context.GetAnimationComponent(x.MeshInfo2.SourceRenderer)) + .ToList()); + if (GetTextureUsageInformations(material, provider) is not { } textureUsageInformations) + unmergeableMaterials.Add(material); + else + usageInformations.Add(material, textureUsageInformations); + } + } + + // remove unmergeable materials and submeshes that have unmergeable materials + { + var processMaterials = new List(unmergeableMaterials); + while (processMaterials.Count != 0) + { + var processSubmeshes = new List(); + + foreach (var processMaterial in processMaterials) + { + if (!materialUsers.Remove(processMaterial, out var users)) continue; + + foreach (var user in users) + processSubmeshes.Add(user); + } + + processMaterials.Clear(); + + foreach (var processSubmesh in processSubmeshes) + { + if (!materialsBySubMesh.Remove(processSubmesh, out var materials)) continue; + + var newUnmergeableMaterials = materials.Where(m => !unmergeableMaterials.Contains(m)).ToList(); + unmergeableMaterials.UnionWith(newUnmergeableMaterials); + processMaterials.AddRange(newUnmergeableMaterials); + } + } + } + + // TODO: implement merging + + foreach (var (material, value) in materialUsers) + { + Debug.Log($"material: {material.name} users: {string.Join(", ", value.Select(x => x.MeshInfo2.SourceRenderer.name))}", material); + } + } + + (bool safeToMerge, IEnumerable materials) GetAnimatedMaterialsForSubMesh( + BuildContext context, Renderer renderer, int materialSlotIndex) + { + var component = context.GetAnimationComponent(renderer); + + if (!component.TryGetObject($"m_Materials.Array.data[{materialSlotIndex}]", out var animation)) + return (safeToMerge: true, Array.Empty()); + + if (animation.ComponentNodes.SingleOrDefault() is AnimatorPropModNode componentNode) + { + if (componentNode.Value.PossibleValues is { } possibleValues) + { + if (possibleValues.All(x => x is Material)) + return (safeToMerge: true, materials: possibleValues.Cast()); + + return (safeToMerge: false, materials: possibleValues.OfType()); + } + else + { + return (safeToMerge: false, materials: Array.Empty()); + } + } + else if (animation.Value.PossibleValues is { } possibleValues) + { + return (safeToMerge: false, materials: possibleValues.OfType()); + } + else if (animation.ComponentNodes.OfType>().FirstOrDefault() is + { } fallbackAnimatorNode) + { + var materials = fallbackAnimatorNode.Value.PossibleValues?.OfType() ?? Array.Empty(); + return (safeToMerge: false, materials); + } + + return (safeToMerge: true, Array.Empty()); + } + + static ShaderKnowledge.TextureUsageInformation[]? + GetTextureUsageInformations(Material material, + ShaderKnowledge.IMaterialPropertyAnimationProvider animationProvider) + { + if (ShaderKnowledge.GetTextureUsageInformationForMaterial(material, animationProvider) + is not { } textureInformations) + return null; + + foreach (var textureInformation in textureInformations) + { + switch (textureInformation.UVChannel) + { + case ShaderKnowledge.UVChannel.UV0: + case ShaderKnowledge.UVChannel.UV1: + case ShaderKnowledge.UVChannel.UV2: + case ShaderKnowledge.UVChannel.UV3: + case ShaderKnowledge.UVChannel.UV4: + case ShaderKnowledge.UVChannel.UV5: + case ShaderKnowledge.UVChannel.UV6: + case ShaderKnowledge.UVChannel.UV7: + case ShaderKnowledge.UVChannel.NonMeshRelated: + break; + case ShaderKnowledge.UVChannel.Unknown: + return null; + default: + throw new ArgumentOutOfRangeException(); + } + } + + return textureInformations; + } + + class MaterialPropertyAnimationProvider : ShaderKnowledge.IMaterialPropertyAnimationProvider + { + private readonly List> _infos; + + public MaterialPropertyAnimationProvider(List> infos) + { + _infos = infos; + } + + public bool IsAnimated(string propertyName) => + _infos.Any(x => x.TryGetFloat($"material.{propertyName}", out _)); } } From 62d323b412bfded31365e83867dea9c9637cb418 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 7 Sep 2024 20:43:49 +0900 Subject: [PATCH 04/30] chore(texture-optimizer): ban optimizing textures used by multiple set of uv --- .../TraceAndOptimize/OptimizeTexture.cs | 116 +++++++++++++++++- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index d88ebdea6..17ffa232b 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -14,6 +14,56 @@ internal class OptimizeTexture : TraceAndOptimizePass { public override string DisplayName => "T&O: OptimizeTexture"; + readonly struct UVID: IEquatable + { + public readonly MeshInfo2? MeshInfo2; + public readonly int SubMeshIndex; + public readonly ShaderKnowledge.UVChannel UVChannel; + + public SubMeshId SubMeshId => MeshInfo2 != null ? new SubMeshId(MeshInfo2!, SubMeshIndex) : throw new InvalidOperationException(); + + public UVID(SubMeshId subMeshId, ShaderKnowledge.UVChannel uvChannel) + : this(subMeshId.MeshInfo2, subMeshId.SubMeshIndex, uvChannel) + { + } + + public UVID(MeshInfo2 meshInfo2, int subMeshIndex, ShaderKnowledge.UVChannel uvChannel) + { + MeshInfo2 = meshInfo2; + SubMeshIndex = subMeshIndex; + UVChannel = uvChannel; + switch (uvChannel) + { + case ShaderKnowledge.UVChannel.UV0: + case ShaderKnowledge.UVChannel.UV1: + case ShaderKnowledge.UVChannel.UV2: + case ShaderKnowledge.UVChannel.UV3: + case ShaderKnowledge.UVChannel.UV4: + case ShaderKnowledge.UVChannel.UV5: + case ShaderKnowledge.UVChannel.UV6: + case ShaderKnowledge.UVChannel.UV7: + break; + case ShaderKnowledge.UVChannel.NonMeshRelated: + MeshInfo2 = null; + SubMeshIndex = -1; + break; + case ShaderKnowledge.UVChannel.Unknown: + default: + throw new ArgumentOutOfRangeException(nameof(uvChannel), uvChannel, null); + } + } + + public bool Equals(UVID other) => MeshInfo2 == other.MeshInfo2 && SubMeshIndex == other.SubMeshIndex && UVChannel == other.UVChannel; + public override bool Equals(object? obj) => obj is UVID other && Equals(other); + public override int GetHashCode() => HashCode.Combine(MeshInfo2, SubMeshIndex, UVChannel); + + public override string ToString() + { + if (MeshInfo2 == null) return UVChannel.ToString(); + return $"{MeshInfo2.SourceRenderer.name} {SubMeshIndex} {UVChannel}"; + } + } + readonly struct SubMeshId : IEquatable { public readonly MeshInfo2 MeshInfo2; @@ -28,6 +78,8 @@ public SubMeshId(MeshInfo2 meshInfo2, int subMeshIndex) public bool Equals(SubMeshId other) => MeshInfo2 == other.MeshInfo2 && SubMeshIndex == other.SubMeshIndex; public override bool Equals(object? obj) => obj is SubMeshId other && Equals(other); public override int GetHashCode() => HashCode.Combine(MeshInfo2, SubMeshIndex); + + public override string ToString() => $"{MeshInfo2.SourceRenderer.name} {SubMeshIndex}"; } protected override void Execute(BuildContext context, TraceAndOptimizeState state) @@ -94,8 +146,8 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat } // collect usageInformation for each material, and add to unmergeableMaterials if it's impossible + var usageInformations = new Dictionary(); { - var usageInformations = new Dictionary(); foreach (var (material, _) in materialUsers) { @@ -109,6 +161,41 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat } } + // for implementation simplicity, we don't support texture(s) that are not used by multiple set of UV + { + var materialsByUSerSubmeshId = new Dictionary, HashSet>(); + foreach (var (material, users) in materialUsers) + { + var set = new EqualsHashSet(users); + if (!materialsByUSerSubmeshId.TryGetValue(set, out var materials)) + materialsByUSerSubmeshId.Add(set, materials = new HashSet()); + materials.Add(material); + } + + var textureUserSets = new Dictionary>>(); + var textureUserMaterials = new Dictionary>(); + foreach (var (key, materials) in materialsByUSerSubmeshId) + { + foreach (var material in materials) + { + foreach (var information in usageInformations[material]) + { + var texture = (Texture2D)material.GetTexture(information.MaterialPropertyName); + if (texture == null) continue; + if (!textureUserSets.TryGetValue(texture, out var users)) + textureUserSets.Add(texture, users = new HashSet>()); + users.Add(key.backedSet.Select(x => new UVID(x, information.UVChannel)).ToEqualsHashSet()); + if (!textureUserMaterials.TryGetValue(texture, out var materialsSet)) + textureUserMaterials.Add(texture, materialsSet = new HashSet()); + materialsSet.Add(material); + } + } + } + + foreach (var (texture, users) in textureUserSets.Where(x => x.Value.Count >= 2)) + unmergeableMaterials.UnionWith(textureUserMaterials[texture]); + } + // remove unmergeable materials and submeshes that have unmergeable materials { var processMaterials = new List(unmergeableMaterials); @@ -138,10 +225,31 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat } // TODO: implement merging - - foreach (var (material, value) in materialUsers) { - Debug.Log($"material: {material.name} users: {string.Join(", ", value.Select(x => x.MeshInfo2.SourceRenderer.name))}", material); + var textureUserMaterials = new Dictionary>(); + var textureByUVs = new Dictionary, HashSet>(); + foreach (var (material, value) in materialUsers) + { + foreach (var information in usageInformations[material]) + { + var texture = (Texture2D)material.GetTexture(information.MaterialPropertyName); + if (texture == null) continue; + + var uvSet = new EqualsHashSet(value.Select(x => new UVID(x, information.UVChannel))); + if (!textureByUVs.TryGetValue(uvSet, out var textures)) + textureByUVs.Add(uvSet, textures = new HashSet()); + textures.Add(texture); + + if (!textureUserMaterials.TryGetValue(texture, out var materials)) + textureUserMaterials.Add(texture, materials = new HashSet<(Material, string)>()); + materials.Add((material, information.MaterialPropertyName)); + } + } + + foreach (var (uvSet, textures) in textureByUVs) + { + Debug.Log($"UVs: {string.Join(", ", uvSet.backedSet)} Textures: {string.Join(", ", textures)}"); + } } } From 398d9765d2676991c831628de25a90ef31f4095d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 8 Sep 2024 17:59:33 +0900 Subject: [PATCH 05/30] feat(optimize-texture): per-texture checking --- .../TraceAndOptimize/OptimizeTexture.cs | 400 +++++++++++++++++- Internal/Utils/Utils.cs | 2 + 2 files changed, 401 insertions(+), 1 deletion(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 17ffa232b..a0c526123 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -1,11 +1,14 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Anatawa12.AvatarOptimizer.AnimatorParsersV2; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; using nadena.dev.ndmf; using UnityEngine; +using UnityEngine.Profiling; +using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; namespace Anatawa12.AvatarOptimizer.Processors.TraceAndOptimizes; @@ -248,7 +251,7 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat foreach (var (uvSet, textures) in textureByUVs) { - Debug.Log($"UVs: {string.Join(", ", uvSet.backedSet)} Textures: {string.Join(", ", textures)}"); + MayAtlasTexture(textures, uvSet.backedSet); } } } @@ -333,4 +336,399 @@ public MaterialPropertyAnimationProvider(List _infos.Any(x => x.TryGetFloat($"material.{propertyName}", out _)); } + + // TODO: uncomment before release + // [Conditional("NEVER_TRUE_VALUE_IS_EXPECTED")] + private static void TraceLog(string message) + { + Debug.Log(message); + } + + struct AtlasResult + { + public Dictionary TextureMapping; + public Dictionary<(Vertex, int uvChannel), Vector2> NewUVs; + + public AtlasResult(Dictionary textureMapping, Dictionary<(Vertex, int uvChannel), Vector2> newUVs) + { + TextureMapping = textureMapping; + NewUVs = newUVs; + } + + public static AtlasResult Empty = new(new Dictionary(), + new Dictionary<(Vertex, int uvChannel), Vector2>()); + } + + static AtlasResult MayAtlasTexture(ICollection textures, ICollection users) + { + if (users.Any(uvid => uvid.UVChannel == ShaderKnowledge.UVChannel.NonMeshRelated)) + return AtlasResult.Empty; + + foreach (var user in users) + { + var submesh = user.MeshInfo2!.SubMeshes[user.SubMeshIndex]; + // currently non triangle topology is not supported + if (submesh.Topology != MeshTopology.Triangles) + return AtlasResult.Empty; + foreach (var vertex in submesh.Vertices) + { + var coord = vertex.GetTexCoord((int)user.UVChannel); + + // UV Tiling is currently not supported + // TODO: if entire island is in n.0<=x= 0 and < 1) || coord.y is not (>= 0 and < 1)) + return AtlasResult.Empty; + } + } + + static IEnumerable TrianglesByUVID(UVID uvid) + { + var submesh = uvid.MeshInfo2!.SubMeshes[uvid.SubMeshIndex]; + for (var index = 0; index < submesh.Vertices.Count; index += 3) + { + var vertex0 = submesh.Vertices[index + 0]; + var vertex1 = submesh.Vertices[index + 1]; + var vertex2 = submesh.Vertices[index + 2]; + yield return new IslandUtility.Triangle((int)uvid.UVChannel, vertex0, vertex1, vertex2); + } + } + + var triangles = users.SelectMany(TrianglesByUVID).ToList(); + var islands = IslandUtility.UVtoIsland(triangles); + + // TODO: merge too over wrapped islands + // We should: merge islands completely inside other island + // We may: merge islands >N% wrapped (heuristic) + + // fit Island bounds to pixel bounds + var maxResolution = -1; + var minResolution = int.MaxValue; + { + foreach (var texture2D in textures) + { + var width = texture2D.width; + var height = texture2D.height; + + if (!width.IsPowerOfTwo() || !height.IsPowerOfTwo()) + { + TraceLog($"{string.Join(", ", textures)} will not merged because {texture2D} is not power of two"); + return AtlasResult.Empty; + } + + maxResolution = Mathf.Max(maxResolution, width, height); + minResolution = Mathf.Min(minResolution, width, height); + } + + // padding is at least 4px with max resolution, 1px in min resolution + const int paddingSize = 4; + + if (minResolution <= paddingSize || maxResolution <= paddingSize) + { + TraceLog( + $"{string.Join(", ", textures)} will not merged because min resolution is less than 4 ({minResolution})"); + return AtlasResult.Empty; + } + + if (maxResolution / paddingSize < minResolution) + minResolution = maxResolution / paddingSize; + + foreach (var island in islands) + { + ref var min = ref island.MinPos; + ref var max = ref island.MaxPos; + + // floor/ceil to pixel bounds and add padding + + min.x = Mathf.Max(Mathf.Floor(min.x * minResolution - 1) / minResolution, 0); + min.y = Mathf.Max(Mathf.Floor(min.y * minResolution - 1) / minResolution, 0); + max.x = Mathf.Min(Mathf.Ceil(max.x * minResolution + 1) / minResolution, 1); + max.y = Mathf.Min(Mathf.Ceil(max.y * minResolution + 1) / minResolution, 1); + } + } + + // Check for island size before trying to atlas + var totalIslandSize = islands.Sum(x => x.Size.x * x.Size.y); + if (totalIslandSize >= 0.5) + { + TraceLog($"{string.Join(", ", textures)} will not merged because more than half ({totalIslandSize}) are used"); + + return AtlasResult.Empty; + } + + var maxIslandLength = islands.Max(x => Mathf.Max(x.Size.x, x.Size.y)); + if (maxIslandLength >= 0.5) + { + TraceLog($"{string.Join(", ", textures)} will not merged because max island length is more than 0.5 ({maxIslandLength})"); + + return AtlasResult.Empty; + } + + TraceLog($"{string.Join(", ", textures)} will go to atlas texture (using {totalIslandSize} of texture)"); + return AtlasResult.Empty; + } + + // Copied from TexTransTool + // https://github.com/ReinaS-64892/TexTransTool/blob/48c608c816c718acc5be607b5c1232870bafc674/TexTransCore/Island/IslandUtility.cs + // Licensed under MIT + // Copyright (c) 2023 Reina_Sakiria + internal static class IslandUtility + { + /// + /// Union-FindアルゴリズムのためのNode Structureです。細かいアロケーションの負荷を避けるために、配列で管理する想定で、 + /// ポインターではなくインデックスで親ノードを指定します。 + /// + /// グループの代表でない限り、parentIndex以外の値は無視されます(古いデータが入る場合があります) + /// + internal struct VertNode + { + public int parentIndex; + + public (Vector2, Vector2) boundingBox; + + public int depth; + public int triCount; + + public Island? island; + + public VertNode(int i, Vector2 uv) + { + parentIndex = i; + boundingBox = (uv, uv); + depth = 0; + island = null; + triCount = 0; + } + + /// + /// 指定したインデックスのノードのグループの代表ノードを調べる + /// + /// + /// + /// + public static int Find(VertNode[] arr, int index) + { + if (arr[index].parentIndex == index) return index; + + return arr[index].parentIndex = Find(arr, arr[index].parentIndex); + } + + /// + /// 指定したふたつのノードを結合する + /// + /// + /// + /// + public static void Merge(VertNode[] arr, int a, int b) + { + a = Find(arr, a); + b = Find(arr, b); + + if (a == b) return; + + if (arr[a].depth < arr[b].depth) + { + (a, b) = (b, a); + } + + if (arr[a].depth == arr[b].depth) arr[a].depth++; + arr[b].parentIndex = a; + + arr[a].boundingBox = (Vector2.Min(arr[a].boundingBox.Item1, arr[b].boundingBox.Item1), + Vector2.Max(arr[a].boundingBox.Item2, arr[b].boundingBox.Item2)); + arr[a].triCount += arr[b].triCount; + } + + /// + /// このグループに該当するIslandに三角面を追加します。Islandが存在しない場合は作成しislandListに追加します。 + /// + /// + /// + public void AddTriangle(Triangle idx, List islandList) + { + if (island == null) + { + islandList.Add(island = new Island()); + island.triangles.Capacity = triCount; + + var min = boundingBox.Item1; + var max = boundingBox.Item2; + + island.MinPos = min; + island.MaxPos = max; + } + + island.triangles.Add(idx); + } + } + + public readonly struct Triangle : IEnumerable + { + public readonly int UVIndex; + public readonly Vertex zero; + public readonly Vertex one; + public readonly Vertex two; + + public Triangle(int uvIndex, Vertex zero, Vertex one, Vertex two) + { + UVIndex = uvIndex; + this.zero = zero; + this.one = one; + this.two = two; + } + + Enumerator GetEnumerator() => new(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + struct Enumerator : IEnumerator + { + private readonly Triangle triangle; + private int index; + + public Enumerator(Triangle triangle) + { + this.triangle = triangle; + index = -1; + } + + public bool MoveNext() + { + index++; + return index < 3; + } + + public void Reset() => index = -1; + + public Vertex Current => index switch + { + 0 => triangle.zero, + 1 => triangle.one, + 2 => triangle.two, + _ => throw new InvalidOperationException(), + }; + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + } + } + + public static List UVtoIsland(ICollection triangles) + { + Profiler.BeginSample("UVtoIsland"); + var islands = UVToIslandImpl(triangles); + Profiler.EndSample(); + + return islands; + } + + private static List UVToIslandImpl(ICollection triangles) + { + // 同一の位置にある頂点をまず調べて、共通のインデックスを割り当てます + Profiler.BeginSample("Preprocess vertices"); + var indexToUv = new List(); + var uvToIndex = new Dictionary(); + var inputVertToUniqueIndex = new List(); + var vertexToUniqueIndex = new Dictionary(); + { + var uniqueUv = 0; + foreach (var triangle in triangles) + { + foreach (var vertex in triangle) + { + var uv = (Vector2)vertex.GetTexCoord(triangle.UVIndex); + if (!uvToIndex.TryGetValue(uv, out var uvVert)) + { + uvToIndex.Add(uv, uvVert = uniqueUv++); + indexToUv.Add(uv); + } + + inputVertToUniqueIndex.Add(uvVert); + vertexToUniqueIndex[vertex] = uvVert; + } + } + } + System.Diagnostics.Debug.Assert(indexToUv.Count == uvToIndex.Count); + System.Diagnostics.Debug.Assert(indexToUv.Count == inputVertToUniqueIndex.Count); + Profiler.EndSample(); + + // Union-Find用のデータストラクチャーを初期化 + Profiler.BeginSample("Init vertNodes"); + var nodes = new VertNode[uvToIndex.Count]; + for (var i = 0; i < nodes.Length; i++) + nodes[i] = new VertNode(i, indexToUv[i]); + Profiler.EndSample(); + + Profiler.BeginSample("Merge vertices"); + foreach (var tri in triangles) + { + int idx_a = vertexToUniqueIndex[tri.zero]; + int idx_b = vertexToUniqueIndex[tri.one]; + int idx_c = vertexToUniqueIndex[tri.two]; + + // 三角面に該当するノードを併合 + VertNode.Merge(nodes, idx_a, idx_b); + VertNode.Merge(nodes, idx_b, idx_c); + + // 際アロケーションを避けるために三角面を数える + nodes[VertNode.Find(nodes, idx_a)].triCount++; + } + + Profiler.EndSample(); + + var islands = new List(); + + // この時点で代表が決まっているので、三角を追加していきます。 + Profiler.BeginSample("Add triangles to islands"); + foreach (var tri in triangles) + { + int idx = vertexToUniqueIndex[tri.zero]; + + nodes[VertNode.Find(nodes, idx)].AddTriangle(tri, islands); + } + + Profiler.EndSample(); + + return islands; + } + + + [Serializable] + public class Island + { + public List triangles; + public Vector2 MinPos; + public Vector2 MaxPos; + + public Vector2 Pivot; + public bool Is90Rotation; + + public Vector2 Size => MaxPos - MinPos; + + public Island(Island source) + { + triangles = new List(source.triangles); + MinPos = source.MinPos; + MaxPos = source.MaxPos; + Is90Rotation = source.Is90Rotation; + } + + public Island(Triangle triangle) + { + triangles = new List { triangle }; + } + + public Island() + { + triangles = new List(); + } + + public Island(List trianglesOfIsland) + { + triangles = trianglesOfIsland; + } + } + } } diff --git a/Internal/Utils/Utils.cs b/Internal/Utils/Utils.cs index ff66dcf14..6fcd0e79f 100644 --- a/Internal/Utils/Utils.cs +++ b/Internal/Utils/Utils.cs @@ -194,5 +194,7 @@ public static EqualsHashSet ToEqualsHashSet(this IEnumerable enumerable public static EqualsHashSet ToEqualsHashSet(this HashSet hashSet) => new EqualsHashSet(hashSet); + + public static bool IsPowerOfTwo(this int x) => x != 0 && (x & (x - 1)) == 0; } } From 3f4466bd76fdf7d7c08edd56b8ce39180802f6f3 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 8 Sep 2024 21:33:38 +0900 Subject: [PATCH 06/30] feat(optimize-texture): implement basic algorithm for atlas --- .../TraceAndOptimize/OptimizeTexture.cs | 122 +++++++++++++++++- Internal/Utils/Utils.cs | 18 +++ 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index a0c526123..86ef0e142 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -397,6 +397,7 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< var islands = IslandUtility.UVtoIsland(triangles); // TODO: merge too over wrapped islands + // https://misskey.niri.la/notes/9xwx6acfid ? // We should: merge islands completely inside other island // We may: merge islands >N% wrapped (heuristic) @@ -464,9 +465,126 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< } TraceLog($"{string.Join(", ", textures)} will go to atlas texture (using {totalIslandSize} of texture)"); + + var atlasIslands = islands.Select(x => new AtlasIsland(x)).ToArray(); + Array.Sort(atlasIslands, (a, b) => b.Size.y.CompareTo(a.Size.y)); + + var maxIslandSizeX = atlasIslands.Max(x => x.Size.x); + var maxIslandSizeY = atlasIslands.Max(x => x.Size.y); + + TraceLog($"Starting Atlas with maxX: {maxIslandSizeX}, maxY: {maxIslandSizeY}"); + + foreach (var atlasSize in AfterAtlasSizesSmallToBig(totalIslandSize, new Vector2(maxIslandSizeX, maxIslandSizeY))) + { + if (TryAtlasTexture(atlasIslands, atlasSize)) + { + // Good News! We did it! + TraceLog($"Good News! We did it!: {atlasSize}"); + return AtlasResult.Empty; + } + else + { + TraceLog($"Failed to atlas with {atlasSize}"); + } + } + + return AtlasResult.Empty; } + static IEnumerable AfterAtlasSizesSmallToBig(float useRatio, Vector2 maxIslandSize) + { + var maxHalfCount = 0; + { + var currentSize = 1f; + while (currentSize > useRatio) + { + maxHalfCount++; + currentSize /= 2; + } + } + + var minXSize = Utils.MinPowerOfTwoGreaterThan(maxIslandSize.x); + var minYSize = Utils.MinPowerOfTwoGreaterThan(maxIslandSize.y); + + for (var halfCount = maxHalfCount; halfCount >= 0; halfCount--) + { + var size = 1f / (1 << halfCount); + + for (var xSize = minXSize; xSize <= 1; xSize *= 2) + { + var ySize = size / xSize; + if (ySize < minYSize) break; + if (ySize > 1) break; + if (ySize >= 1 && xSize >= 1) break; + + yield return new Vector2(xSize, ySize); + } + } + } + + // expecting y size sorted + static bool TryAtlasTexture(AtlasIsland[] islands, Vector2 size) + { + var done = new bool[islands.Length]; + var doneCount = 0; + + var yCursor = 0f; + + while (true) + { + var firstNotFinished = Array.FindIndex(done, x => !x); + + // this means all islands are finished + if (firstNotFinished == -1) break; + + var firstNotFinishedIsland = islands[firstNotFinished]; + + if (yCursor + firstNotFinishedIsland.Size.y > size.y) + return false; // failed to atlas + + + var xCursor = 0f; + + firstNotFinishedIsland.Pivot = new Vector2(xCursor, yCursor); + xCursor += firstNotFinishedIsland.Size.x; + done[firstNotFinished] = true; + doneCount++; + + for (var i = firstNotFinished + 1; i < islands.Length; i++) + { + if (done[i]) continue; + + var island = islands[i]; + if (xCursor + island.Size.x > size.x) continue; + + island.Pivot = new Vector2(xCursor, yCursor); + xCursor += island.Size.x; + done[i] = true; + doneCount++; + } + + yCursor += firstNotFinishedIsland.Size.y; + } + + // all islands are placed + return true; + } + + class AtlasIsland + { + //TODO: rotate + public IslandUtility.Island OriginalIsland; + public Vector2 Pivot; + + public Vector2 Size => OriginalIsland.Size; + + public AtlasIsland(IslandUtility.Island originalIsland) + { + OriginalIsland = originalIsland; + } + } + // Copied from TexTransTool // https://github.com/ReinaS-64892/TexTransTool/blob/48c608c816c718acc5be607b5c1232870bafc674/TexTransCore/Island/IslandUtility.cs // Licensed under MIT @@ -702,9 +820,6 @@ public class Island public Vector2 MinPos; public Vector2 MaxPos; - public Vector2 Pivot; - public bool Is90Rotation; - public Vector2 Size => MaxPos - MinPos; public Island(Island source) @@ -712,7 +827,6 @@ public Island(Island source) triangles = new List(source.triangles); MinPos = source.MinPos; MaxPos = source.MaxPos; - Is90Rotation = source.Is90Rotation; } public Island(Triangle triangle) diff --git a/Internal/Utils/Utils.cs b/Internal/Utils/Utils.cs index 6fcd0e79f..9c089e53a 100644 --- a/Internal/Utils/Utils.cs +++ b/Internal/Utils/Utils.cs @@ -196,5 +196,23 @@ public static EqualsHashSet ToEqualsHashSet(this HashSet hashSet) => new EqualsHashSet(hashSet); public static bool IsPowerOfTwo(this int x) => x != 0 && (x & (x - 1)) == 0; + + public static float MinPowerOfTwoGreaterThan(float x) + { + if (x <= 0) throw new ArgumentOutOfRangeException(nameof(x), x, "x must be positive"); + + if (x < 1) + { + var r = 1f; + while (r / 2 > x) r /= 2; + return r; + } + else + { + var r = 1f; + while (r < x) r *= 2; + return r; + } + } } } From 2747310477d3756cabeec403b206ea28d56131ac Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 9 Sep 2024 12:48:09 +0900 Subject: [PATCH 07/30] chore: clone material before processing / parsing material --- Editor/OptimizerPlugin.cs | 1 + Editor/Processors/DupliacteAssets.cs | 98 +++++++++++++++++++++++ Editor/Processors/DupliacteAssets.cs.meta | 3 + Internal/Utils/DeepCloneHelper.cs | 3 +- 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 Editor/Processors/DupliacteAssets.cs create mode 100644 Editor/Processors/DupliacteAssets.cs.meta diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index fbdd64962..3e50a2b85 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -54,6 +54,7 @@ protected override void Configure() }) .Then.Run("Validation", (ctx) => ComponentValidation.ValidateAll(ctx.AvatarRootObject)) .Then.Run(Processors.TraceAndOptimizes.LoadTraceAndOptimizeConfiguration.Instance) + .Then.Run(Processors.DupliacteAssets.Instance) .Then.Run(Processors.ParseAnimator.Instance) .Then.Run(Processors.TraceAndOptimizes.AddRemoveEmptySubMesh.Instance) .Then.Run(Processors.TraceAndOptimizes.AutoFreezeBlendShape.Instance) diff --git a/Editor/Processors/DupliacteAssets.cs b/Editor/Processors/DupliacteAssets.cs new file mode 100644 index 000000000..6d0881b89 --- /dev/null +++ b/Editor/Processors/DupliacteAssets.cs @@ -0,0 +1,98 @@ +using System; +using Anatawa12.AvatarOptimizer.ndmf; +using nadena.dev.ndmf; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Anatawa12.AvatarOptimizer.Processors; + +/// +/// The class to clone something for future in-place modification +/// +/// Currently this class is intended to clone +/// +public class DupliacteAssets : Pass +{ + protected override void Execute(BuildContext context) + { + if (!context.GetState().Enabled) return; + + var cloner = new Cloner(); + + foreach (var component in context.GetComponents()) + { + switch (component) + { + case SkinnedMeshRenderer renderer: + { + var meshInfo2 = context.GetMeshInfoFor(renderer); + foreach (var subMesh in meshInfo2.SubMeshes) + foreach (ref var material in subMesh.SharedMaterials.AsSpan()) + material = cloner.MapObject(material); + } + break; + default: + { + using var serializedObject = new SerializedObject(component); + + foreach (var objectReferenceProperty in serializedObject.ObjectReferenceProperties()) + { + objectReferenceProperty.objectReferenceValue = cloner.MapObject(objectReferenceProperty.objectReferenceValue); + } + + serializedObject.ApplyModifiedPropertiesWithoutUndo(); + + break; + } + } + } + } + + class Cloner : DeepCloneHelper + { + protected override Object? CustomClone(Object o) => null; + + protected override ComponentSupport GetComponentSupport(Object o) + { + switch (o) + { + // Target Objects + case Material: + return ComponentSupport.Clone; + + // intermediate objects + case Motion: + case AnimatorController: + case AnimatorOverrideController: + case AnimatorState: + case AnimatorStateMachine: + case AnimatorTransitionBase: + case StateMachineBehaviour: + +#if AAO_VRM0 + case VRM.BlendShapeAvatar: + case VRM.BlendShapeClip: +#endif +#if AAO_VRM1 + case UniVRM10.VRM10Object: + case UniVRM10.VRM10Expression: +#endif + return ComponentSupport.Clone; + + case Texture: + case MonoScript: + case Component: + case GameObject: + return ComponentSupport.NoClone; + + case ScriptableObject: + return ComponentSupport.NoClone; + + default: + return ComponentSupport.NoClone; + } + } + } +} diff --git a/Editor/Processors/DupliacteAssets.cs.meta b/Editor/Processors/DupliacteAssets.cs.meta new file mode 100644 index 000000000..a97024a1f --- /dev/null +++ b/Editor/Processors/DupliacteAssets.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 978d36a7fdd54fcb9d4b38ecda447cb5 +timeCreated: 1725799283 \ No newline at end of file diff --git a/Internal/Utils/DeepCloneHelper.cs b/Internal/Utils/DeepCloneHelper.cs index c7ec6b3b9..3786616ea 100644 --- a/Internal/Utils/DeepCloneHelper.cs +++ b/Internal/Utils/DeepCloneHelper.cs @@ -13,7 +13,8 @@ public abstract class DeepCloneHelper private readonly Dictionary _cache = new Dictionary(); private bool _mapped = false; - public T MapObject(T obj) where T : Object + [return:NotNullIfNotNull("obj")] + public T? MapObject(T? obj) where T : Object { using (ErrorReport.WithContextObject(obj)) return DeepClone(obj); From 7a7691da852e7eea42eea696aa3697fd06bc72d0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 9 Sep 2024 15:21:18 +0900 Subject: [PATCH 08/30] feat: implement uv packing / atlas --- Assets/merge-texture-helper.shader | 5 +- .../TraceAndOptimize/OptimizeTexture.cs | 123 +++++++++++++++++- Editor/ShaderKnowledge.cs | 7 + 3 files changed, 131 insertions(+), 4 deletions(-) diff --git a/Assets/merge-texture-helper.shader b/Assets/merge-texture-helper.shader index a767f407c..a567220dc 100644 --- a/Assets/merge-texture-helper.shader +++ b/Assets/merge-texture-helper.shader @@ -5,6 +5,7 @@ Shader "Hidden/merge_texture_helper" _MainTex ("Texture", 2D) = "white" {} // x, y, w, h _Rect ("Rectangle", Vector) = (0, 0, 1, 1) + _SrcRect ("SourceRectangle", Vector) = (0, 0, 1, 1) } SubShader { @@ -35,6 +36,7 @@ Shader "Hidden/merge_texture_helper" sampler2D _MainTex; float4 _MainTex_ST; float4 _Rect; + float4 _SrcRect; v2f vert (appdata v) { @@ -47,7 +49,8 @@ Shader "Hidden/merge_texture_helper" fixed4 frag (v2f i) : SV_Target { - fixed4 c = tex2D(_MainTex, TRANSFORM_TEX(i.uv, _MainTex)); + float2 uv = i.uv * _SrcRect.zw + _SrcRect.xy; + fixed4 c = tex2D(_MainTex, uv); clip(c.a - 0.0001); return c; } diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 86ef0e142..fa5d62511 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -6,7 +6,9 @@ using Anatawa12.AvatarOptimizer.AnimatorParsersV2; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; using nadena.dev.ndmf; +using UnityEditor; using UnityEngine; +using UnityEngine.Experimental.Rendering; using UnityEngine.Profiling; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; @@ -227,7 +229,6 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat } } - // TODO: implement merging { var textureUserMaterials = new Dictionary>(); var textureByUVs = new Dictionary, HashSet>(); @@ -249,9 +250,36 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat } } + var textureMapping = new Dictionary(); + var atlasResults = new Dictionary, AtlasResult>(); + foreach (var (uvSet, textures) in textureByUVs) { - MayAtlasTexture(textures, uvSet.backedSet); + var atlasResult = MayAtlasTexture(textures, uvSet.backedSet); + + if (atlasResult.IsEmpty()) continue; + + atlasResults.Add(uvSet, atlasResult); + foreach (var (key, value) in atlasResult.TextureMapping) + textureMapping.Add(key, value); + } + + // TODO: duplicate vertex if used by multiple UVs + + foreach (var (_, result) in atlasResults) + { + foreach (var ((vertex, uvChannel), newUV) in result.NewUVs) + { + vertex.SetTexCoord(uvChannel, newUV); + } + } + + foreach (var (original, users) in textureUserMaterials) + { + if (!textureMapping.TryGetValue(original, out var newTexture)) continue; + + foreach (var (material, propertyName) in users) + material.SetTexture(propertyName, newTexture); } } } @@ -357,6 +385,10 @@ public AtlasResult(Dictionary textureMapping, Dictionary<( public static AtlasResult Empty = new(new Dictionary(), new Dictionary<(Vertex, int uvChannel), Vector2>()); + + public bool IsEmpty() => + (TextureMapping == null || TextureMapping.Count == 0) && + (NewUVs == null || NewUVs.Count == 0); } static AtlasResult MayAtlasTexture(ICollection textures, ICollection users) @@ -480,7 +512,7 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< { // Good News! We did it! TraceLog($"Good News! We did it!: {atlasSize}"); - return AtlasResult.Empty; + return BuildAtlasResult(atlasIslands, atlasSize, textures); } else { @@ -492,6 +524,91 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< return AtlasResult.Empty; } + private static Material? _helperMaterial; + + private static Material HelperMaterial => + _helperMaterial != null ? _helperMaterial : _helperMaterial = new Material(Assets.MergeTextureHelper); + private static readonly int MainTexProp = Shader.PropertyToID("_MainTex"); + private static readonly int MainTexStProp = Shader.PropertyToID("_MainTex_ST"); + private static readonly int RectProp = Shader.PropertyToID("_Rect"); + private static readonly int SrcRectProp = Shader.PropertyToID("_SrcRect"); + + private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures) + { + var textureMapping = new Dictionary(); + var newUVs = new Dictionary<(Vertex, int uvChannel), Vector2>(); + + foreach (var texture2D in textures) + { + var newWidth = Mathf.CeilToInt(atlasSize.x * texture2D.width); + var newHeight = Mathf.CeilToInt(atlasSize.y * texture2D.height); + var target = new RenderTexture(newWidth, newHeight, 0, GraphicsFormat.R8G8B8A8_SRGB); + HelperMaterial.SetTexture(MainTexProp, texture2D); + + // TODO: block copying texture + + foreach (var atlasIsland in atlasIslands) + { + //HelperMaterial.SetVector(MainTexStProp, + // new Vector4(atlasIsland.OriginalIsland.Size.x, atlasIsland.OriginalIsland.Size.y, + // atlasIsland.OriginalIsland.MinPos.x, atlasIsland.OriginalIsland.MinPos.y)); + + HelperMaterial.SetVector(SrcRectProp, + new Vector4(atlasIsland.OriginalIsland.MinPos.x, atlasIsland.OriginalIsland.MinPos.y, + atlasIsland.OriginalIsland.Size.x, atlasIsland.OriginalIsland.Size.y)); + + var pivot = atlasIsland.Pivot / atlasSize; + var size = atlasIsland.Size / atlasSize; + + HelperMaterial.SetVector(RectProp, new Vector4(pivot.x, pivot.y, size.x, size.y)); + + Graphics.Blit(texture2D, target, HelperMaterial); + } + + var newTexture = CopyFromRenderTarget(target); + + if (GraphicsFormatUtility.IsCompressedFormat(texture2D.format)) + EditorUtility.CompressTexture(newTexture, texture2D.format, TextureCompressionQuality.Normal); + newTexture.name = texture2D.name + " (AAO UV Packed)"; + newTexture.Apply(true); + textureMapping.Add(texture2D, newTexture); + } + + foreach (var atlasIsland in atlasIslands) + foreach (var triangle in atlasIsland.OriginalIsland.triangles) + foreach (var vertex in triangle) + { + var uv = (Vector2)vertex.GetTexCoord(triangle.UVIndex); + + uv -= atlasIsland.OriginalIsland.MinPos; + uv += atlasIsland.Pivot; + uv /= atlasSize; + + newUVs.TryAdd((vertex, triangle.UVIndex), uv); + } + + return new AtlasResult(textureMapping, newUVs); + } + + private static Texture2D CopyFromRenderTarget(RenderTexture source) + { + var prev = RenderTexture.active; + var texture = new Texture2D(source.width, source.height, TextureFormat.RGBA32, true); + + try + { + RenderTexture.active = source; + texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); + texture.Apply(); + } + finally + { + RenderTexture.active = prev; + } + + return texture; + } + static IEnumerable AfterAtlasSizesSmallToBig(float useRatio, Vector2 maxIslandSize) { var maxHalfCount = 0; diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index f40b7e120..661e5924b 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -215,6 +215,13 @@ public TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) information.Add(new TextureUsageInformation("_OutlineWidthMask", uvMain)); // ?? } + // emission + if (material.GetInt("_UseEmission") != 0 || animation.IsAnimated("_UseEmission")) + { + information.Add(new TextureUsageInformation("_EmissionMap", uvMain)); + information.Add(new TextureUsageInformation("_EmissionBlendMask", uvMain)); // ?? + } + // TODO: Many Properties return information.ToArray(); } From 9782a24622e8f8767f30e6c9479fd40fe9c7ca5a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 9 Sep 2024 16:04:05 +0900 Subject: [PATCH 09/30] feat: duplicate vertex if used by multiple vertices --- .../TraceAndOptimize/OptimizeTexture.cs | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index fa5d62511..39cb223ec 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -264,13 +264,58 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat textureMapping.Add(key, value); } - // TODO: duplicate vertex if used by multiple UVs + var used = new HashSet(); - foreach (var (_, result) in atlasResults) + // collect vertices used by non-atlas submeshes { - foreach (var ((vertex, uvChannel), newUV) in result.NewUVs) + var uvids = atlasResults.Keys.SelectMany(x => x.backedSet); + var mergingSubMeshes = uvids.Select(x => x.MeshInfo2!.SubMeshes[x.SubMeshIndex]).ToHashSet(); + var meshes = uvids.Select(x => x.MeshInfo2!).Distinct(); + + foreach (var mesh in meshes) + foreach (var subMesh in mesh.SubMeshes) + if (!mergingSubMeshes.Contains(subMesh)) + used.UnionWith(subMesh.Vertices); + } + + foreach (var (uvids, result) in atlasResults) + { + var newVertexMap = new Dictionary(); + + foreach (var uvid in uvids.backedSet) { - vertex.SetTexCoord(uvChannel, newUV); + var meshInfo2 = uvid.MeshInfo2!; + var submesh = meshInfo2.SubMeshes[uvid.SubMeshIndex]; + for (var i = 0; i < submesh.Vertices.Count; i++) + { + var originalVertex = submesh.Vertices[i]; + var newUVList = result.NewUVs[originalVertex]; + + Vertex vertex; + if (newVertexMap.TryGetValue(originalVertex, out vertex)) + { + // use cloned vertex + } + else + { + if (used.Add(originalVertex)) + { + vertex = originalVertex; + newVertexMap.Add(originalVertex, vertex); + } + else + { + vertex = originalVertex.Clone(); + newVertexMap.Add(originalVertex, vertex); + meshInfo2.Vertices.Add(vertex); + TraceLog("Duplicating vertex"); + } + + foreach (var (uvChannel, newUV) in newUVList) + vertex.SetTexCoord(uvChannel, newUV); + } + submesh.Vertices[i] = vertex; + } } } @@ -375,16 +420,16 @@ private static void TraceLog(string message) struct AtlasResult { public Dictionary TextureMapping; - public Dictionary<(Vertex, int uvChannel), Vector2> NewUVs; + public Dictionary> NewUVs; - public AtlasResult(Dictionary textureMapping, Dictionary<(Vertex, int uvChannel), Vector2> newUVs) + public AtlasResult(Dictionary textureMapping, Dictionary> newUVs) { TextureMapping = textureMapping; NewUVs = newUVs; } public static AtlasResult Empty = new(new Dictionary(), - new Dictionary<(Vertex, int uvChannel), Vector2>()); + new Dictionary>()); public bool IsEmpty() => (TextureMapping == null || TextureMapping.Count == 0) && @@ -536,7 +581,6 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures) { var textureMapping = new Dictionary(); - var newUVs = new Dictionary<(Vertex, int uvChannel), Vector2>(); foreach (var texture2D in textures) { @@ -574,6 +618,8 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 textureMapping.Add(texture2D, newTexture); } + var newUVs = new Dictionary>(); + foreach (var atlasIsland in atlasIslands) foreach (var triangle in atlasIsland.OriginalIsland.triangles) foreach (var vertex in triangle) @@ -584,7 +630,9 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 uv += atlasIsland.Pivot; uv /= atlasSize; - newUVs.TryAdd((vertex, triangle.UVIndex), uv); + if (!newUVs.TryGetValue(vertex, out var newUVList)) + newUVs.Add(vertex, newUVList = new List<(int uvChannel, Vector2 newUV)>()); + newUVList.Add((triangle.UVIndex, uv)); } return new AtlasResult(textureMapping, newUVs); From 066030e61aa2eda4e82a30c8ac2b863f9c52fff6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Sep 2024 16:11:31 +0900 Subject: [PATCH 10/30] feat(optimize-texture): completely lossless per-block copying texture --- .../TraceAndOptimize/OptimizeTexture.cs | 156 ++++++++++++++---- Internal/Utils/Utils.cs | 43 +++++ 2 files changed, 164 insertions(+), 35 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 39cb223ec..dd225151c 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -497,30 +497,47 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< minResolution = Mathf.Min(minResolution, width, height); } + var xBlockSizeInMaxResolutionLCM = 1; + var yBlockSizeInMaxResolutionLCM = 1; + + foreach (var texture2D in textures) + { + var width = texture2D.width; + var height = texture2D.height; + + var xBlockSizeInMaxResolution = (int)(GraphicsFormatUtility.GetBlockWidth(texture2D.format) * (maxResolution / width)); + var yBlockSizeInMaxResolution = (int)(GraphicsFormatUtility.GetBlockHeight(texture2D.format) * (maxResolution / height)); + + xBlockSizeInMaxResolutionLCM = Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, xBlockSizeInMaxResolution); + yBlockSizeInMaxResolutionLCM = Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, yBlockSizeInMaxResolution); + } + // padding is at least 4px with max resolution, 1px in min resolution - const int paddingSize = 4; + var minResolutionPixelSizeInMaxResolution = maxResolution / minResolution; + var paddingSize = Mathf.Max(minResolutionPixelSizeInMaxResolution, maxResolution / 100); if (minResolution <= paddingSize || maxResolution <= paddingSize) { TraceLog( - $"{string.Join(", ", textures)} will not merged because min resolution is less than 4 ({minResolution})"); + $"{string.Join(", ", textures)} will not merged because min resolution is less than {paddingSize} ({minResolution})"); return AtlasResult.Empty; } - if (maxResolution / paddingSize < minResolution) - minResolution = maxResolution / paddingSize; + var blockSizeX = Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); + var blockSizeY = Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); + + TraceLog($"blockSizeX: {blockSizeX}, blockSizeY: {blockSizeY}"); foreach (var island in islands) { ref var min = ref island.MinPos; ref var max = ref island.MaxPos; - // floor/ceil to pixel bounds and add padding - - min.x = Mathf.Max(Mathf.Floor(min.x * minResolution - 1) / minResolution, 0); - min.y = Mathf.Max(Mathf.Floor(min.y * minResolution - 1) / minResolution, 0); - max.x = Mathf.Min(Mathf.Ceil(max.x * minResolution + 1) / minResolution, 1); - max.y = Mathf.Min(Mathf.Ceil(max.y * minResolution + 1) / minResolution, 1); + // fit to block size + min.x = Mathf.Floor((min.x * maxResolution - paddingSize) / blockSizeX) * blockSizeX / maxResolution; + min.y = Mathf.Floor((min.y * maxResolution - paddingSize) / blockSizeY) * blockSizeY / maxResolution; + max.x = Mathf.Ceil((max.x * maxResolution + paddingSize) / blockSizeX) * blockSizeX / maxResolution; + max.y = Mathf.Ceil((max.y * maxResolution + paddingSize) / blockSizeY) * blockSizeY / maxResolution; } } @@ -557,7 +574,7 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< { // Good News! We did it! TraceLog($"Good News! We did it!: {atlasSize}"); - return BuildAtlasResult(atlasIslands, atlasSize, textures); + return BuildAtlasResult(atlasIslands, atlasSize, textures, useBlockCopying: true); } else { @@ -578,43 +595,112 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< private static readonly int RectProp = Shader.PropertyToID("_Rect"); private static readonly int SrcRectProp = Shader.PropertyToID("_SrcRect"); - private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures) + private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures, bool useBlockCopying = false) { var textureMapping = new Dictionary(); foreach (var texture2D in textures) { - var newWidth = Mathf.CeilToInt(atlasSize.x * texture2D.width); - var newHeight = Mathf.CeilToInt(atlasSize.y * texture2D.height); - var target = new RenderTexture(newWidth, newHeight, 0, GraphicsFormat.R8G8B8A8_SRGB); - HelperMaterial.SetTexture(MainTexProp, texture2D); + var newWidth = (int)(atlasSize.x * texture2D.width); + var newHeight = (int)(atlasSize.y * texture2D.height); + Texture2D newTexture; - // TODO: block copying texture - - foreach (var atlasIsland in atlasIslands) + if (useBlockCopying) { - //HelperMaterial.SetVector(MainTexStProp, - // new Vector4(atlasIsland.OriginalIsland.Size.x, atlasIsland.OriginalIsland.Size.y, - // atlasIsland.OriginalIsland.MinPos.x, atlasIsland.OriginalIsland.MinPos.y)); - - HelperMaterial.SetVector(SrcRectProp, - new Vector4(atlasIsland.OriginalIsland.MinPos.x, atlasIsland.OriginalIsland.MinPos.y, - atlasIsland.OriginalIsland.Size.x, atlasIsland.OriginalIsland.Size.y)); + var mipmapCount = Mathf.Min(Utils.MostSignificantBit(Mathf.Min(newWidth, newHeight)), texture2D.mipmapCount); + + var destMipmapSize = GraphicsFormatUtility.ComputeMipmapSize(newWidth, newHeight, texture2D.format); + var sourceMipmapSize = GraphicsFormatUtility.ComputeMipmapSize(texture2D.width, texture2D.height, texture2D.format); + + Texture2D readableVersion; + if (texture2D.isReadable) + { + readableVersion = texture2D; + } + else + { + readableVersion = new Texture2D(texture2D.width, texture2D.height, texture2D.format, texture2D.mipmapCount, !texture2D.isDataSRGB); + Graphics.CopyTexture(texture2D, readableVersion); + readableVersion.Apply(false); + } + var sourceTextureData = readableVersion.GetRawTextureData(); // TODO: this does not work if texture is not readable + var sourceTextureDataSpan = sourceTextureData.AsSpan().Slice(0, (int)sourceMipmapSize); + + var destTextureData = new byte[(int)destMipmapSize]; + var destTextureDataSpan = destTextureData.AsSpan(); - var pivot = atlasIsland.Pivot / atlasSize; - var size = atlasIsland.Size / atlasSize; + TraceLog($"MipmapSize for {newWidth}x{newHeight} is {destMipmapSize} and data is {sourceTextureData.Length}"); - HelperMaterial.SetVector(RectProp, new Vector4(pivot.x, pivot.y, size.x, size.y)); + var blockWidth = (int)GraphicsFormatUtility.GetBlockWidth(texture2D.format); + var blockHeight = (int)GraphicsFormatUtility.GetBlockHeight(texture2D.format); + var blockSize = (int)GraphicsFormatUtility.GetBlockSize(texture2D.format); - Graphics.Blit(texture2D, target, HelperMaterial); + var destTextureBlockStride = (newWidth + blockWidth - 1) / blockWidth * blockSize; + var sourceTextureBlockStride = (texture2D.width + blockWidth - 1) / blockWidth * blockSize; + + foreach (var atlasIsland in atlasIslands) + { + var xPixelCount = (int)(atlasIsland.Size.x * texture2D.width); + var yPixelCount = (int)(atlasIsland.Size.y * texture2D.height); + // in most cases same as xPixelCount / blockWidth but if block size is not 2^n, it may be different + var xBlockCount = (xPixelCount + blockWidth - 1) / blockWidth; + var yBlockCount = (yPixelCount + blockHeight - 1) / blockHeight; + + var sourceXPixelPosition = (int)(atlasIsland.OriginalIsland.MinPos.x * texture2D.width); + var sourceYPixelPosition = (int)(atlasIsland.OriginalIsland.MinPos.y * texture2D.height); + var sourceXBlockPosition = sourceXPixelPosition / blockWidth; + var sourceYBlockPosition = sourceYPixelPosition / blockHeight; + + var destXPixelPosition = (int)(atlasIsland.Pivot.x * texture2D.width); + var destYPixelPosition = (int)(atlasIsland.Pivot.y * texture2D.height); + var destXBlockPosition = destXPixelPosition / blockWidth; + var destYBlockPosition = destYPixelPosition / blockHeight; + + var xBlockByteCount = xBlockCount * blockSize; + for (var y = 0; y < yBlockCount; y++) + { + var sourceY = sourceYBlockPosition + y; + var destY = destYBlockPosition + y; + + var sourceSpan = sourceTextureDataSpan.Slice(sourceY * sourceTextureBlockStride + sourceXBlockPosition * blockSize, xBlockByteCount); + var destSpan = destTextureDataSpan.Slice(destY * destTextureBlockStride + destXBlockPosition * blockSize, xBlockByteCount); + + sourceSpan.CopyTo(destSpan); + } + } + + newTexture = new Texture2D(newWidth, newHeight, texture2D.format, mipmapCount, !texture2D.isDataSRGB); + newTexture.SetPixelData(destTextureData, 0); + newTexture.Apply(true, !texture2D.isReadable); + // TODO: fix broken mipmaps } + else + { + var target = new RenderTexture(newWidth, newHeight, 0, GraphicsFormat.R8G8B8A8_SRGB); + HelperMaterial.SetTexture(MainTexProp, texture2D); + + foreach (var atlasIsland in atlasIslands) + { + HelperMaterial.SetVector(SrcRectProp, + new Vector4(atlasIsland.OriginalIsland.MinPos.x, atlasIsland.OriginalIsland.MinPos.y, + atlasIsland.OriginalIsland.Size.x, atlasIsland.OriginalIsland.Size.y)); + + var pivot = atlasIsland.Pivot / atlasSize; + var size = atlasIsland.Size / atlasSize; + + HelperMaterial.SetVector(RectProp, new Vector4(pivot.x, pivot.y, size.x, size.y)); - var newTexture = CopyFromRenderTarget(target); + Graphics.Blit(texture2D, target, HelperMaterial); + } + + newTexture = CopyFromRenderTarget(target); + + if (GraphicsFormatUtility.IsCompressedFormat(texture2D.format)) + EditorUtility.CompressTexture(newTexture, texture2D.format, TextureCompressionQuality.Normal); + newTexture.name = texture2D.name + " (AAO UV Packed)"; + newTexture.Apply(true, !texture2D.isReadable); + } - if (GraphicsFormatUtility.IsCompressedFormat(texture2D.format)) - EditorUtility.CompressTexture(newTexture, texture2D.format, TextureCompressionQuality.Normal); - newTexture.name = texture2D.name + " (AAO UV Packed)"; - newTexture.Apply(true); textureMapping.Add(texture2D, newTexture); } diff --git a/Internal/Utils/Utils.cs b/Internal/Utils/Utils.cs index 9c089e53a..52591c35b 100644 --- a/Internal/Utils/Utils.cs +++ b/Internal/Utils/Utils.cs @@ -214,5 +214,48 @@ public static float MinPowerOfTwoGreaterThan(float x) return r; } } + + public static int LeastCommonMultiple(params int[] numbers) => numbers.Length == 0 ? 0 : numbers.Aggregate(LeastCommonMultiple); + + public static int LeastCommonMultiple(int a, int b) + { + if (a == 0 || b == 0) return 0; + return Math.Abs(a * b) / GreatestCommonDivisor(a, b); + } + + public static int GreatestCommonDivisor(int a, int b) + { + if (a == 0) return b; + if (b == 0) return a; + while (b != 0) + { + var t = b; + b = a % b; + a = t; + } + + return a; + } + + public static int MostSignificantBit(int x) => MostSignificantBit((uint)x); + + public static int MostSignificantBit(uint x) + { + // https://github.com/microsoft/mimalloc/blob/fab7329c7a3eee64e455e0a7aea7566eb2038cf3/src/page-queue.c#L67-L80 + + // de Bruijn multiplication, see + ReadOnlySpan debruijn = new byte[]{ + 31, 0, 22, 1, 28, 23, 18, 2, 29, 26, 24, 10, 19, 7, 3, 12, + 30, 21, 27, 17, 25, 9, 6, 11, 20, 16, 8, 5, 15, 4, 14, 13, + }; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return debruijn[(int)((x * 0x076be629) >> 27)]; + } } } From eacf3eb97c7a8e7cf43442ebf3edef59fb984b05 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Sep 2024 21:34:23 +0900 Subject: [PATCH 11/30] feat(optimize-texture): fix clipping --- Assets/merge-texture-helper.shader | 5 ++++- Editor/Processors/TraceAndOptimize/OptimizeTexture.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Assets/merge-texture-helper.shader b/Assets/merge-texture-helper.shader index a567220dc..2176d426d 100644 --- a/Assets/merge-texture-helper.shader +++ b/Assets/merge-texture-helper.shader @@ -6,6 +6,7 @@ Shader "Hidden/merge_texture_helper" // x, y, w, h _Rect ("Rectangle", Vector) = (0, 0, 1, 1) _SrcRect ("SourceRectangle", Vector) = (0, 0, 1, 1) + _NoClip ("NoClip", Int) = 0 } SubShader { @@ -37,6 +38,7 @@ Shader "Hidden/merge_texture_helper" float4 _MainTex_ST; float4 _Rect; float4 _SrcRect; + int _NoClip; v2f vert (appdata v) { @@ -51,7 +53,8 @@ Shader "Hidden/merge_texture_helper" { float2 uv = i.uv * _SrcRect.zw + _SrcRect.xy; fixed4 c = tex2D(_MainTex, uv); - clip(c.a - 0.0001); + if (_NoClip == 0) + clip(c.a - 0.0001); return c; } ENDCG diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index dd225151c..caa498a6a 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -591,9 +591,9 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< private static Material HelperMaterial => _helperMaterial != null ? _helperMaterial : _helperMaterial = new Material(Assets.MergeTextureHelper); private static readonly int MainTexProp = Shader.PropertyToID("_MainTex"); - private static readonly int MainTexStProp = Shader.PropertyToID("_MainTex_ST"); private static readonly int RectProp = Shader.PropertyToID("_Rect"); private static readonly int SrcRectProp = Shader.PropertyToID("_SrcRect"); + private static readonly int NoClipProp = Shader.PropertyToID("_NoClip"); private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures, bool useBlockCopying = false) { @@ -678,6 +678,7 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 { var target = new RenderTexture(newWidth, newHeight, 0, GraphicsFormat.R8G8B8A8_SRGB); HelperMaterial.SetTexture(MainTexProp, texture2D); + HelperMaterial.SetInt(NoClipProp, 1); foreach (var atlasIsland in atlasIslands) { From 820c054aee24a3c973ee65c38945a3d1cc3fc3cc Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Sep 2024 21:35:53 +0900 Subject: [PATCH 12/30] feat(optimize-texture): gave up implementing mipmap with block copying --- Editor/Processors/TraceAndOptimize/OptimizeTexture.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index caa498a6a..2044423c9 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -605,9 +605,9 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 var newHeight = (int)(atlasSize.y * texture2D.height); Texture2D newTexture; - if (useBlockCopying) + var mipmapCount = Mathf.Min(Utils.MostSignificantBit(Mathf.Min(newWidth, newHeight)), texture2D.mipmapCount); + if (useBlockCopying && GraphicsFormatUtility.IsCompressedFormat(texture2D.format) && mipmapCount == 1) { - var mipmapCount = Mathf.Min(Utils.MostSignificantBit(Mathf.Min(newWidth, newHeight)), texture2D.mipmapCount); var destMipmapSize = GraphicsFormatUtility.ComputeMipmapSize(newWidth, newHeight, texture2D.format); var sourceMipmapSize = GraphicsFormatUtility.ComputeMipmapSize(texture2D.width, texture2D.height, texture2D.format); @@ -672,7 +672,6 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 newTexture = new Texture2D(newWidth, newHeight, texture2D.format, mipmapCount, !texture2D.isDataSRGB); newTexture.SetPixelData(destTextureData, 0); newTexture.Apply(true, !texture2D.isReadable); - // TODO: fix broken mipmaps } else { From 6d69a4253df4e3fe039f216a12f861f3f806fde8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Sep 2024 21:57:39 +0900 Subject: [PATCH 13/30] chore: make DupliacteAssets internal --- Editor/Processors/DupliacteAssets.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/Processors/DupliacteAssets.cs b/Editor/Processors/DupliacteAssets.cs index 6d0881b89..84018aac2 100644 --- a/Editor/Processors/DupliacteAssets.cs +++ b/Editor/Processors/DupliacteAssets.cs @@ -13,7 +13,7 @@ namespace Anatawa12.AvatarOptimizer.Processors; /// /// Currently this class is intended to clone /// -public class DupliacteAssets : Pass +internal class DupliacteAssets : Pass { protected override void Execute(BuildContext context) { From e74b5b493f63fabb5c989d806526272213bc9e0b Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 16:13:20 +0900 Subject: [PATCH 14/30] feat(optimize-texture): use 1px texture if it's monotone --- .../TraceAndOptimize/OptimizeTexture.cs | 90 +++++++++++++++++-- 1 file changed, 83 insertions(+), 7 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 2044423c9..c15064922 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -19,6 +19,17 @@ internal class OptimizeTexture : TraceAndOptimizePass { public override string DisplayName => "T&O: OptimizeTexture"; + protected override void Execute(BuildContext context, TraceAndOptimizeState state) + { + if (!state.OptimizeTexture) return; + new OptimizeTextureImpl().Execute(context, state); + } +} + +internal struct OptimizeTextureImpl { + private Dictionary<(Color c, bool isSrgb), Texture2D>? _colorTextures; + private Dictionary<(Color c, bool isSrgb), Texture2D> ColorTextures => _colorTextures ??= new Dictionary<(Color c, bool isSrgb), Texture2D>(); + readonly struct UVID: IEquatable { public readonly MeshInfo2? MeshInfo2; @@ -87,7 +98,7 @@ public SubMeshId(MeshInfo2 meshInfo2, int subMeshIndex) public override string ToString() => $"{MeshInfo2.SourceRenderer.name} {SubMeshIndex}"; } - protected override void Execute(BuildContext context, TraceAndOptimizeState state) + internal void Execute(BuildContext context, TraceAndOptimizeState state) { if (!state.OptimizeTexture) return; @@ -436,7 +447,7 @@ public bool IsEmpty() => (NewUVs == null || NewUVs.Count == 0); } - static AtlasResult MayAtlasTexture(ICollection textures, ICollection users) + AtlasResult MayAtlasTexture(ICollection textures, ICollection users) { if (users.Any(uvid => uvid.UVChannel == ShaderKnowledge.UVChannel.NonMeshRelated)) return AtlasResult.Empty; @@ -595,7 +606,7 @@ static AtlasResult MayAtlasTexture(ICollection textures, ICollection< private static readonly int SrcRectProp = Shader.PropertyToID("_SrcRect"); private static readonly int NoClipProp = Shader.PropertyToID("_NoClip"); - private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures, bool useBlockCopying = false) + private AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSize, ICollection textures, bool useBlockCopying = false) { var textureMapping = new Dictionary(); @@ -679,6 +690,8 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 HelperMaterial.SetTexture(MainTexProp, texture2D); HelperMaterial.SetInt(NoClipProp, 1); + bool isBlack = true; + foreach (var atlasIsland in atlasIslands) { HelperMaterial.SetVector(SrcRectProp, @@ -695,10 +708,35 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 newTexture = CopyFromRenderTarget(target); - if (GraphicsFormatUtility.IsCompressedFormat(texture2D.format)) - EditorUtility.CompressTexture(newTexture, texture2D.format, TextureCompressionQuality.Normal); - newTexture.name = texture2D.name + " (AAO UV Packed)"; - newTexture.Apply(true, !texture2D.isReadable); + if (IsSingleColor(newTexture, atlasIslands, atlasSize, out var color)) + { + // if color is consist of 0 or 1, isSrgb not matters so assume it's linear + var isSrgb = texture2D.isDataSRGB && color is not { r: 0 or 1, g: 0 or 1, b: 0 or 1 }; + + if (color is { r: 0, g: 0, b: 0, a: 0 }) + newTexture = Texture2D.blackTexture; + else if (color is { r: 1, g: 1, b: 1, a: 1 }) + newTexture = Texture2D.whiteTexture; + else if (color is { r: 1, g: 0, b: 0, a: 0 }) + newTexture = Texture2D.redTexture; + else if (ColorTextures.TryGetValue((color, isSrgb), out var cachedTexture)) + newTexture = cachedTexture; + else + { + newTexture = new Texture2D(1, 1, TextureFormat.RGBA32, false, !isSrgb); + newTexture.SetPixel(0, 0, color); + newTexture.Apply(false, true); + newTexture.name = $"AAO Monotone {color} {(isSrgb ? "sRGB" : "Linear")}"; + ColorTextures.Add((color, isSrgb), newTexture); + } + } + else + { + if (GraphicsFormatUtility.IsCompressedFormat(texture2D.format)) + EditorUtility.CompressTexture(newTexture, texture2D.format, TextureCompressionQuality.Normal); + newTexture.name = texture2D.name + " (AAO UV Packed)"; + newTexture.Apply(true, !texture2D.isReadable); + } } textureMapping.Add(texture2D, newTexture); @@ -724,6 +762,44 @@ private static AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 return new AtlasResult(textureMapping, newUVs); } + private static bool IsSingleColor(Texture2D texture, AtlasIsland[] islands, Vector2 atlasSize, out Color color) + { + Color? commonColor = null; + + var texSize = new Vector2(texture.width, texture.height); + + var colors = texture.GetPixels(); + + foreach (var atlasIsland in islands) + { + var pivot = atlasIsland.Pivot / atlasSize * texSize; + var pivotInt = new Vector2Int(Mathf.FloorToInt(pivot.x), Mathf.FloorToInt(pivot.y)); + var size = atlasIsland.Size / atlasSize * texSize; + var sizeInt = new Vector2Int(Mathf.CeilToInt(size.x), Mathf.CeilToInt(size.y)); + + for (var dy = 0; dy < sizeInt.y; dy++) + for (var dx = 0; dx < sizeInt.x; dx++) + { + var y = pivotInt.y + dy; + var x = pivotInt.x + dx; + var colorAt = colors[y * texture.width + x]; + + if (commonColor is not { } c) + { + commonColor = colorAt; + } + else if (c != colorAt) + { + color = default; + return false; + } + } + } + + color = commonColor ?? default; + return true; + } + private static Texture2D CopyFromRenderTarget(RenderTexture source) { var prev = RenderTexture.active; From 1aced2fddb2140958b6123ef08d9c20151a075d7 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 17:50:50 +0900 Subject: [PATCH 15/30] feat(optimize-texture): remove hard-coded texture formats for better compatibility --- .../TraceAndOptimize/OptimizeTexture.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index c15064922..65920edc4 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -686,7 +686,11 @@ private AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSi } else { - var target = new RenderTexture(newWidth, newHeight, 0, GraphicsFormat.R8G8B8A8_SRGB); + var format = SystemInfo.GetCompatibleFormat( + GraphicsFormatUtility.GetGraphicsFormat(texture2D.format, isSRGB: texture2D.isDataSRGB), + FormatUsage.Render); + TraceLog($"Using format {format} ({texture2D.format})"); + using var tempTexture = Utils.TemporaryRenderTexture(newWidth, newHeight, depthBuffer: 0, format: format); HelperMaterial.SetTexture(MainTexProp, texture2D); HelperMaterial.SetInt(NoClipProp, 1); @@ -703,10 +707,10 @@ private AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSi HelperMaterial.SetVector(RectProp, new Vector4(pivot.x, pivot.y, size.x, size.y)); - Graphics.Blit(texture2D, target, HelperMaterial); + Graphics.Blit(texture2D, tempTexture.RenderTexture, HelperMaterial); } - newTexture = CopyFromRenderTarget(target); + newTexture = CopyFromRenderTarget(tempTexture.RenderTexture, texture2D); if (IsSingleColor(newTexture, atlasIslands, atlasSize, out var color)) { @@ -800,11 +804,13 @@ private static bool IsSingleColor(Texture2D texture, AtlasIsland[] islands, Vect return true; } - private static Texture2D CopyFromRenderTarget(RenderTexture source) + private static Texture2D CopyFromRenderTarget(RenderTexture source, Texture2D original) { var prev = RenderTexture.active; - var texture = new Texture2D(source.width, source.height, TextureFormat.RGBA32, true); - + var format = SystemInfo.GetCompatibleFormat(original.graphicsFormat, FormatUsage.ReadPixels); + var textureFormat = GraphicsFormatUtility.GetTextureFormat(format); + var texture = new Texture2D(source.width, source.height, textureFormat, true, linear: !source.isDataSRGB); + try { RenderTexture.active = source; From 77a09784686fc7a12cbfc14f513abfdbda8b7f05 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 17:57:22 +0900 Subject: [PATCH 16/30] feat(internal): temporaryRenderTexture scope --- .../Utils/Utils.TemporaryRenderTexture.cs | 109 ++++++++++++++++++ .../Utils.TemporaryRenderTexture.cs.meta | 3 + 2 files changed, 112 insertions(+) create mode 100644 Internal/Utils/Utils.TemporaryRenderTexture.cs create mode 100644 Internal/Utils/Utils.TemporaryRenderTexture.cs.meta diff --git a/Internal/Utils/Utils.TemporaryRenderTexture.cs b/Internal/Utils/Utils.TemporaryRenderTexture.cs new file mode 100644 index 000000000..79e15c7fd --- /dev/null +++ b/Internal/Utils/Utils.TemporaryRenderTexture.cs @@ -0,0 +1,109 @@ +using System; +using UnityEngine; +using UnityEngine.Experimental.Rendering; + +namespace Anatawa12.AvatarOptimizer; + +partial class Utils +{ + private static TemporaryRenderTextureScope TemporaryRenderTextureImpl( + int width, + int height, + GraphicsFormat depthStencilFormat, + GraphicsFormat colorFormat, + int antiAliasing = 1, + RenderTextureMemoryless memorylessMode = RenderTextureMemoryless.None, + VRTextureUsage vrUsage = VRTextureUsage.None, + bool useDynamicScale = false) + { + return TemporaryRenderTexture(new RenderTextureDescriptor(width, height, colorFormat, depthStencilFormat) + { + msaaSamples = antiAliasing, + memoryless = memorylessMode, + vrUsage = vrUsage, + useDynamicScale = useDynamicScale + }); + } + + internal static GraphicsFormat GetDepthStencilFormatLegacy( + int depthBits, + GraphicsFormat colorFormat) + { + return colorFormat == GraphicsFormat.ShadowAuto + ? GraphicsFormatUtility.GetDepthStencilFormat(depthBits, 0) + : GraphicsFormatUtility.GetDepthStencilFormat(depthBits, 8); + } + + internal static GraphicsFormat GetDepthStencilFormatLegacy( + int depthBits, + RenderTextureFormat format) + { + return GetDepthStencilFormatLegacy(depthBits, format == RenderTextureFormat.Shadowmap); + } + + internal static GraphicsFormat GetDepthStencilFormatLegacy( + int depthBits, + bool requestedShadowMap) + { + return requestedShadowMap ? GraphicsFormatUtility.GetDepthStencilFormat(depthBits, 0) : GraphicsFormatUtility.GetDepthStencilFormat(depthBits, 8); + } + + public static TemporaryRenderTextureScope TemporaryRenderTexture( + int width, + int height, + int depthBuffer = 0, + RenderTextureFormat format = RenderTextureFormat.Default, + RenderTextureReadWrite readWrite = RenderTextureReadWrite.Default, + int antiAliasing = 1, + RenderTextureMemoryless memorylessMode = RenderTextureMemoryless.None, + VRTextureUsage vrUsage = VRTextureUsage.None, + bool useDynamicScale = false) + { + var compatibleFormat = GetCompatibleFormat(format, readWrite); + var stencilFormatLegacy = GetDepthStencilFormatLegacy(depthBuffer, format); + + return TemporaryRenderTextureImpl(width, height, stencilFormatLegacy, compatibleFormat, antiAliasing, memorylessMode, vrUsage, useDynamicScale); + } + + public static TemporaryRenderTextureScope TemporaryRenderTexture( + int width, + int height, + int depthBuffer, + GraphicsFormat format, + int antiAliasing = 1, + RenderTextureMemoryless memorylessMode = RenderTextureMemoryless.None, + VRTextureUsage vrUsage = VRTextureUsage.None, + bool useDynamicScale = false) + { + return TemporaryRenderTextureImpl(width, height, GetDepthStencilFormatLegacy(depthBuffer, format), format, antiAliasing, memorylessMode, vrUsage, useDynamicScale); + } + + private static GraphicsFormat GetCompatibleFormat( + RenderTextureFormat renderTextureFormat, + RenderTextureReadWrite readWrite) + { + var graphicsFormat = GraphicsFormatUtility.GetGraphicsFormat(renderTextureFormat, readWrite); + var compatibleFormat = SystemInfo.GetCompatibleFormat(graphicsFormat, FormatUsage.Render); + if (graphicsFormat == compatibleFormat) + return graphicsFormat; + return compatibleFormat; + } + + public static TemporaryRenderTextureScope TemporaryRenderTexture(RenderTextureDescriptor descriptor) => + new(RenderTexture.GetTemporary(descriptor)); + + public struct TemporaryRenderTextureScope : IDisposable + { + public RenderTexture RenderTexture { get; } + + internal TemporaryRenderTextureScope(RenderTexture texture) + { + RenderTexture = texture; + } + + public void Dispose() + { + RenderTexture.ReleaseTemporary(RenderTexture); + } + } +} diff --git a/Internal/Utils/Utils.TemporaryRenderTexture.cs.meta b/Internal/Utils/Utils.TemporaryRenderTexture.cs.meta new file mode 100644 index 000000000..3ef782367 --- /dev/null +++ b/Internal/Utils/Utils.TemporaryRenderTexture.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 619813dd6d5444d69fc91a8dc4d9d38b +timeCreated: 1726039129 \ No newline at end of file From cb07a8d263ced0fa9a88b0fa18f1b57e072485ba Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 18:13:45 +0900 Subject: [PATCH 17/30] feat(optimize-texture): islands that is completely inside other islands --- .../TraceAndOptimize/OptimizeTexture.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 65920edc4..dcd048de6 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -484,10 +484,29 @@ AtlasResult MayAtlasTexture(ICollection textures, ICollection u var triangles = users.SelectMany(TrianglesByUVID).ToList(); var islands = IslandUtility.UVtoIsland(triangles); - // TODO: merge too over wrapped islands - // https://misskey.niri.la/notes/9xwx6acfid ? - // We should: merge islands completely inside other island - // We may: merge islands >N% wrapped (heuristic) + for (var i = 0; i < islands.Count; i++) + { + var islandI = islands[i]; + for (var j = 0; j < islands.Count; j++) + { + if (i == j) continue; + + var islandJ = islands[j]; + + // if islandJ is completely inside islandI, merge islandJ to islandI + if (islandI.MinPos.x <= islandJ.MinPos.x && islandJ.MaxPos.x <= islandI.MaxPos.x && + islandI.MinPos.y <= islandJ.MinPos.y && islandJ.MaxPos.y <= islandI.MaxPos.y) + { + islandI.triangles.AddRange(islandJ.triangles); + islands.RemoveAt(j); + j--; + if (j < i) i--; + } + } + } + + // TODO: We may merge islands >N% wrapped (heuristic merge islands) + // This mage can be after fitting to block size // fit Island bounds to pixel bounds var maxResolution = -1; From d846348e12f46199a8549c2ee95564283c4bc743 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 18:21:07 +0900 Subject: [PATCH 18/30] chore(optimize-texture): remove or implement small TODOs --- Editor/Processors/TraceAndOptimize/OptimizeTexture.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index dcd048de6..ea06984cf 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -421,8 +421,7 @@ public bool IsAnimated(string propertyName) => _infos.Any(x => x.TryGetFloat($"material.{propertyName}", out _)); } - // TODO: uncomment before release - // [Conditional("NEVER_TRUE_VALUE_IS_EXPECTED")] + [Conditional("AAO_OPTIMIZE_TEXTURE_TRACE_LOG")] private static void TraceLog(string message) { Debug.Log(message); @@ -653,7 +652,7 @@ private AtlasResult BuildAtlasResult(AtlasIsland[] atlasIslands, Vector2 atlasSi Graphics.CopyTexture(texture2D, readableVersion); readableVersion.Apply(false); } - var sourceTextureData = readableVersion.GetRawTextureData(); // TODO: this does not work if texture is not readable + var sourceTextureData = readableVersion.GetRawTextureData(); var sourceTextureDataSpan = sourceTextureData.AsSpan().Slice(0, (int)sourceMipmapSize); var destTextureData = new byte[(int)destMipmapSize]; From b2786ea98d4c3ba73c7475d44910c56f49767213 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Sep 2024 20:12:07 +0900 Subject: [PATCH 19/30] refactor(optimize-texture): split to multiple functions --- .../TraceAndOptimize/OptimizeTexture.cs | 213 ++++++++++-------- 1 file changed, 122 insertions(+), 91 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index ea06984cf..1c93f52f6 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -451,12 +451,51 @@ AtlasResult MayAtlasTexture(ICollection textures, ICollection u if (users.Any(uvid => uvid.UVChannel == ShaderKnowledge.UVChannel.NonMeshRelated)) return AtlasResult.Empty; + if (CreateIslands(users) is not {} islands) + return AtlasResult.Empty; + + MergeIslands(islands); + + if (ComputeBlockSize(textures) is not var (blockSizeRatioX, blockSizeRatioY, paddingRatio)) + return AtlasResult.Empty; + + FitToBlockSizeAndAddPadding(islands, blockSizeRatioX, blockSizeRatioY, paddingRatio); + + var atlasIslands = islands.Select(x => new AtlasIsland(x)).ToArray(); + Array.Sort(atlasIslands, (a, b) => b.Size.y.CompareTo(a.Size.y)); + + if (AfterAtlasSizesSmallToBig(atlasIslands) is not {} atlasSizes) + { + TraceLog($"{string.Join(", ", textures)} will not merged because island size does not fit criteria"); + return AtlasResult.Empty; + } + + foreach (var atlasSize in atlasSizes) + { + if (TryAtlasTexture(atlasIslands, atlasSize)) + { + // Good News! We did it! + TraceLog($"Good News! We did it!: {atlasSize}"); + return BuildAtlasResult(atlasIslands, atlasSize, textures, useBlockCopying: true); + } + else + { + TraceLog($"Failed to atlas with {atlasSize}"); + } + } + + + return AtlasResult.Empty; + } + + private List? CreateIslands(ICollection users) + { foreach (var user in users) { var submesh = user.MeshInfo2!.SubMeshes[user.SubMeshIndex]; // currently non triangle topology is not supported if (submesh.Topology != MeshTopology.Triangles) - return AtlasResult.Empty; + return null; foreach (var vertex in submesh.Vertices) { var coord = vertex.GetTexCoord((int)user.UVChannel); @@ -464,7 +503,7 @@ AtlasResult MayAtlasTexture(ICollection textures, ICollection u // UV Tiling is currently not supported // TODO: if entire island is in n.0<=x= 0 and < 1) || coord.y is not (>= 0 and < 1)) - return AtlasResult.Empty; + return null; } } @@ -483,6 +522,14 @@ AtlasResult MayAtlasTexture(ICollection textures, ICollection u var triangles = users.SelectMany(TrianglesByUVID).ToList(); var islands = IslandUtility.UVtoIsland(triangles); + return islands; + } + + private void MergeIslands(List islands) + { + // TODO: We may merge islands >N% wrapped (heuristic merge islands) + // This mage can be after fitting to block size + for (var i = 0; i < islands.Count; i++) { var islandI = islands[i]; @@ -503,116 +550,83 @@ AtlasResult MayAtlasTexture(ICollection textures, ICollection u } } } + } - // TODO: We may merge islands >N% wrapped (heuristic merge islands) - // This mage can be after fitting to block size - - // fit Island bounds to pixel bounds + private (float blockSizeRatioX, float blockSizeRatioY, float paddingRatio)? ComputeBlockSize(ICollection textures) + { var maxResolution = -1; var minResolution = int.MaxValue; - { - foreach (var texture2D in textures) - { - var width = texture2D.width; - var height = texture2D.height; - - if (!width.IsPowerOfTwo() || !height.IsPowerOfTwo()) - { - TraceLog($"{string.Join(", ", textures)} will not merged because {texture2D} is not power of two"); - return AtlasResult.Empty; - } - - maxResolution = Mathf.Max(maxResolution, width, height); - minResolution = Mathf.Min(minResolution, width, height); - } - - var xBlockSizeInMaxResolutionLCM = 1; - var yBlockSizeInMaxResolutionLCM = 1; - foreach (var texture2D in textures) - { - var width = texture2D.width; - var height = texture2D.height; - - var xBlockSizeInMaxResolution = (int)(GraphicsFormatUtility.GetBlockWidth(texture2D.format) * (maxResolution / width)); - var yBlockSizeInMaxResolution = (int)(GraphicsFormatUtility.GetBlockHeight(texture2D.format) * (maxResolution / height)); - - xBlockSizeInMaxResolutionLCM = Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, xBlockSizeInMaxResolution); - yBlockSizeInMaxResolutionLCM = Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, yBlockSizeInMaxResolution); - } - - // padding is at least 4px with max resolution, 1px in min resolution - var minResolutionPixelSizeInMaxResolution = maxResolution / minResolution; - var paddingSize = Mathf.Max(minResolutionPixelSizeInMaxResolution, maxResolution / 100); + foreach (var texture2D in textures) + { + var width = texture2D.width; + var height = texture2D.height; - if (minResolution <= paddingSize || maxResolution <= paddingSize) + if (!width.IsPowerOfTwo() || !height.IsPowerOfTwo()) { - TraceLog( - $"{string.Join(", ", textures)} will not merged because min resolution is less than {paddingSize} ({minResolution})"); - return AtlasResult.Empty; + TraceLog($"{string.Join(", ", textures)} will not merged because {texture2D} is not power of two"); + return null; } - var blockSizeX = Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); - var blockSizeY = Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); - - TraceLog($"blockSizeX: {blockSizeX}, blockSizeY: {blockSizeY}"); - - foreach (var island in islands) - { - ref var min = ref island.MinPos; - ref var max = ref island.MaxPos; - - // fit to block size - min.x = Mathf.Floor((min.x * maxResolution - paddingSize) / blockSizeX) * blockSizeX / maxResolution; - min.y = Mathf.Floor((min.y * maxResolution - paddingSize) / blockSizeY) * blockSizeY / maxResolution; - max.x = Mathf.Ceil((max.x * maxResolution + paddingSize) / blockSizeX) * blockSizeX / maxResolution; - max.y = Mathf.Ceil((max.y * maxResolution + paddingSize) / blockSizeY) * blockSizeY / maxResolution; - } + maxResolution = Mathf.Max(maxResolution, width, height); + minResolution = Mathf.Min(minResolution, width, height); } - // Check for island size before trying to atlas - var totalIslandSize = islands.Sum(x => x.Size.x * x.Size.y); - if (totalIslandSize >= 0.5) - { - TraceLog($"{string.Join(", ", textures)} will not merged because more than half ({totalIslandSize}) are used"); + var xBlockSizeInMaxResolutionLCM = 1; + var yBlockSizeInMaxResolutionLCM = 1; - return AtlasResult.Empty; + foreach (var texture2D in textures) + { + var width = texture2D.width; + var height = texture2D.height; + + var xBlockSizeInMaxResolution = + (int)(GraphicsFormatUtility.GetBlockWidth(texture2D.format) * (maxResolution / width)); + var yBlockSizeInMaxResolution = + (int)(GraphicsFormatUtility.GetBlockHeight(texture2D.format) * (maxResolution / height)); + + xBlockSizeInMaxResolutionLCM = + Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, xBlockSizeInMaxResolution); + yBlockSizeInMaxResolutionLCM = + Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, yBlockSizeInMaxResolution); } - var maxIslandLength = islands.Max(x => Mathf.Max(x.Size.x, x.Size.y)); - if (maxIslandLength >= 0.5) - { - TraceLog($"{string.Join(", ", textures)} will not merged because max island length is more than 0.5 ({maxIslandLength})"); + // padding is at least 4px with max resolution, 1px in min resolution + var minResolutionPixelSizeInMaxResolution = maxResolution / minResolution; + var paddingSize = Mathf.Max(minResolutionPixelSizeInMaxResolution, maxResolution / 100); - return AtlasResult.Empty; + if (minResolution <= paddingSize || maxResolution <= paddingSize) + { + TraceLog( + $"{string.Join(", ", textures)} will not merged because min resolution is less than {paddingSize} ({minResolution})"); + return null; } - TraceLog($"{string.Join(", ", textures)} will go to atlas texture (using {totalIslandSize} of texture)"); + var blockSizeX = Utils.LeastCommonMultiple(xBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); + var blockSizeY = Utils.LeastCommonMultiple(yBlockSizeInMaxResolutionLCM, minResolutionPixelSizeInMaxResolution); - var atlasIslands = islands.Select(x => new AtlasIsland(x)).ToArray(); - Array.Sort(atlasIslands, (a, b) => b.Size.y.CompareTo(a.Size.y)); + var blockSizeRatioX = (float)blockSizeX / maxResolution; + var blockSizeRatioY = (float)blockSizeY / maxResolution; + var paddingRatio = (float)paddingSize / maxResolution; - var maxIslandSizeX = atlasIslands.Max(x => x.Size.x); - var maxIslandSizeY = atlasIslands.Max(x => x.Size.y); + TraceLog($"blockSizeX: {blockSizeX}, blockSizeY: {blockSizeY}"); - TraceLog($"Starting Atlas with maxX: {maxIslandSizeX}, maxY: {maxIslandSizeY}"); + return (blockSizeRatioX, blockSizeRatioY, paddingRatio); + } - foreach (var atlasSize in AfterAtlasSizesSmallToBig(totalIslandSize, new Vector2(maxIslandSizeX, maxIslandSizeY))) + private void FitToBlockSizeAndAddPadding(List islands, float blockSizeRatioX, float blockSizeRatioY, float paddingRatio) + { + foreach (var island in islands) { - if (TryAtlasTexture(atlasIslands, atlasSize)) - { - // Good News! We did it! - TraceLog($"Good News! We did it!: {atlasSize}"); - return BuildAtlasResult(atlasIslands, atlasSize, textures, useBlockCopying: true); - } - else - { - TraceLog($"Failed to atlas with {atlasSize}"); - } + ref var min = ref island.MinPos; + ref var max = ref island.MaxPos; + + // fit to block size + min.x = Mathf.Floor(min.x / blockSizeRatioX - paddingRatio) * blockSizeRatioX; + min.y = Mathf.Floor(min.y / blockSizeRatioY - paddingRatio) * blockSizeRatioY; + max.x = Mathf.Ceil(max.x / blockSizeRatioX + paddingRatio) * blockSizeRatioX; + max.y = Mathf.Ceil(max.y / blockSizeRatioY + paddingRatio) * blockSizeRatioY; } - - - return AtlasResult.Empty; } private static Material? _helperMaterial; @@ -843,7 +857,24 @@ private static Texture2D CopyFromRenderTarget(RenderTexture source, Texture2D or return texture; } - static IEnumerable AfterAtlasSizesSmallToBig(float useRatio, Vector2 maxIslandSize) + static IEnumerable? AfterAtlasSizesSmallToBig(AtlasIsland[] islands) + { + // Check for island size before trying to atlas + var totalIslandSize = islands.Sum(x => x.Size.x * x.Size.y); + if (totalIslandSize >= 0.5) return null; + + var maxIslandLength = islands.Max(x => Mathf.Max(x.Size.x, x.Size.y)); + if (maxIslandLength >= 0.5) return null; + + var maxIslandSizeX = islands.Max(x => x.Size.x); + var maxIslandSizeY = islands.Max(x => x.Size.y); + + TraceLog($"Starting Atlas with maxX: {maxIslandSizeX}, maxY: {maxIslandSizeY}"); + + return AfterAtlasSizesSmallToBigGenerator(totalIslandSize, new Vector2(maxIslandSizeX, maxIslandSizeY)); + } + + static IEnumerable AfterAtlasSizesSmallToBigGenerator(float useRatio, Vector2 maxIslandSize) { var maxHalfCount = 0; { From d66e30b328e983dcf784137305a09d764f8dbd56 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Sep 2024 15:04:14 +0900 Subject: [PATCH 20/30] chore: change API for MaterialPropertyAnimationProvider --- .../TraceAndOptimize/OptimizeTexture.cs | 31 ++++-- Editor/ShaderKnowledge.cs | 103 +++++++++++------- 2 files changed, 83 insertions(+), 51 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 1c93f52f6..c9d6b5893 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -167,10 +167,11 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) foreach (var (material, _) in materialUsers) { - var provider = new MaterialPropertyAnimationProvider( + var provider = new TextureUsageInformationCallback( + material, materialUsers[material].Select(x => context.GetAnimationComponent(x.MeshInfo2.SourceRenderer)) .ToList()); - if (GetTextureUsageInformations(material, provider) is not { } textureUsageInformations) + if (GetTextureUsageInformations(provider) is not { } textureUsageInformations) unmergeableMaterials.Add(material); else usageInformations.Add(material, textureUsageInformations); @@ -377,10 +378,9 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) } static ShaderKnowledge.TextureUsageInformation[]? - GetTextureUsageInformations(Material material, - ShaderKnowledge.IMaterialPropertyAnimationProvider animationProvider) + GetTextureUsageInformations(ShaderKnowledge.TextureUsageInformationCallback callback) { - if (ShaderKnowledge.GetTextureUsageInformationForMaterial(material, animationProvider) + if (ShaderKnowledge.GetTextureUsageInformationForMaterial(callback) is not { } textureInformations) return null; @@ -408,17 +408,30 @@ static ShaderKnowledge.TextureUsageInformation[]? return textureInformations; } - class MaterialPropertyAnimationProvider : ShaderKnowledge.IMaterialPropertyAnimationProvider + class TextureUsageInformationCallback : ShaderKnowledge.TextureUsageInformationCallback { + private readonly Material _material; private readonly List> _infos; - public MaterialPropertyAnimationProvider(List> infos) + public TextureUsageInformationCallback(Material material, List> infos) { + _material = material; _infos = infos; } - public bool IsAnimated(string propertyName) => - _infos.Any(x => x.TryGetFloat($"material.{propertyName}", out _)); + public override Shader Shader => _material.shader; + + private T? GetValue(string propertyName, Func computer, bool considerAnimation) where T : struct + { + // animated; return null + if (considerAnimation && _infos.Any(x => x.TryGetFloat($"material.{propertyName}", out _))) + return null; + return computer(propertyName); + } + + public override int? GetInteger(string propertyName, bool considerAnimation = true) => GetValue(propertyName, _material.GetInt, considerAnimation); + + public override float? GetFloat(string propertyName, bool considerAnimation = true) => GetValue(propertyName, _material.GetFloat, considerAnimation); } [Conditional("AAO_OPTIMIZE_TEXTURE_TRACE_LOG")] diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index 661e5924b..7ebeb470e 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -100,9 +100,36 @@ private static bool CheckAffectedUvDiscard(Material material, MeshInfo2 meshInfo }); } - public interface IMaterialPropertyAnimationProvider + public abstract class TextureUsageInformationCallback { - bool IsAnimated(string propertyName); + internal TextureUsageInformationCallback() { } + + public abstract Shader Shader { get; } + + /// + /// Returns the integer value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + public abstract int? GetInteger(string propertyName, bool considerAnimation = true); + + /// + /// Returns the float value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + public abstract float? GetFloat(string propertyName, bool considerAnimation = true); + + /// + /// Creates a new instance. + /// + /// The name of the texture property in the material. + /// The UV channel for the texture. + /// A new instance. + public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, + UVChannel uvChannel) => new(materialPropertyName, uvChannel); } public enum UVChannel @@ -125,7 +152,7 @@ public class TextureUsageInformation public string MaterialPropertyName { get; } public UVChannel UVChannel { get; } - public TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) + internal TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) { MaterialPropertyName = materialPropertyName; UVChannel = uvChannel; @@ -136,20 +163,19 @@ public TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) /// /// Returns texture usage information for the material. /// - /// /// null if the shader is not supported - public static TextureUsageInformation[]? GetTextureUsageInformationForMaterial(Material material, IMaterialPropertyAnimationProvider animation) + public static TextureUsageInformation[]? GetTextureUsageInformationForMaterial(TextureUsageInformationCallback information) { - if (AssetDatabase.GetAssetPath(material.shader).StartsWith("Packages/jp.lilxyzw.liltoon")) + if (AssetDatabase.GetAssetPath(information.Shader).StartsWith("Packages/jp.lilxyzw.liltoon")) { // it looks liltoon! - return GetTextureUsageInformationForMaterialLiltoon(material, animation); + return GetTextureUsageInformationForMaterialLiltoon(information); } return null; } - private static TextureUsageInformation[]? GetTextureUsageInformationForMaterialLiltoon(Material material, IMaterialPropertyAnimationProvider animation) + private static TextureUsageInformation[]? GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) { // This implementation is made for my Anon + Wahuku for testing this feature. // TODO: version check @@ -158,68 +184,61 @@ public TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) var uvMain = UVChannel.UV0; // TODO: UV Animation, angle (_MainTex_ScrollRotate) , Tilting, Offsets (_ST) for MainTex - information.Add(new TextureUsageInformation("_DitherTex", UVChannel.NonMeshRelated)); - information.Add(new TextureUsageInformation("_MainTex", uvMain)); - information.Add(new TextureUsageInformation("_MainColorAdjustMask", uvMain)); - information.Add(new TextureUsageInformation("_MainGradationTex", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_DitherTex", UVChannel.NonMeshRelated)); + information.Add(matInfo.CreateTextureUsageInformation("_MainTex", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_MainColorAdjustMask", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_MainGradationTex", uvMain)); - if (material.GetInt("_UseMain2ndTex") != 0 || animation.IsAnimated("_UseMain2ndTex")) + if (matInfo.GetInteger("_UseMain2ndTex") != 0) { UVChannel main2ndUV; - if (animation.IsAnimated("_Main2ndTex_UVMode")) + switch (matInfo.GetInteger("_Main2ndTex_UVMode")) { - main2ndUV = UVChannel.Unknown; + case 0: main2ndUV = UVChannel.UV0; break; + case 1: main2ndUV = UVChannel.UV1; break; + case 2: main2ndUV = UVChannel.UV2; break; + case 3: main2ndUV = UVChannel.UV3; break; + case 4: main2ndUV = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) + default: main2ndUV = UVChannel.Unknown; break; } - else - { - switch (material.GetInt("_Main2ndTex_UVMode")) - { - case 0: main2ndUV = UVChannel.UV0; break; - case 1: main2ndUV = UVChannel.UV1; break; - case 2: main2ndUV = UVChannel.UV2; break; - case 3: main2ndUV = UVChannel.UV3; break; - case 4: main2ndUV = UVChannel.NonMeshRelated; break; - default: main2ndUV = UVChannel.Unknown; break; - } - } - information.Add(new TextureUsageInformation("_Main2ndTex", main2ndUV)); + information.Add(matInfo.CreateTextureUsageInformation("_Main2ndTex", main2ndUV)); // TODO: UV Animation, angle (_MainTex2_ScrollRotate) , Tilting, Offsets (_ST) for MainTex2 - information.Add(new TextureUsageInformation("_Main2ndBlendMask", main2ndUV)); // NO ScaleOffset - information.Add(new TextureUsageInformation("_Main2ndDissolveMask", main2ndUV)); - information.Add(new TextureUsageInformation("_Main2ndDissolveNoiseMask", main2ndUV)); + information.Add(matInfo.CreateTextureUsageInformation("_Main2ndBlendMask", main2ndUV)); // NO ScaleOffset + information.Add(matInfo.CreateTextureUsageInformation("_Main2ndDissolveMask", main2ndUV)); + information.Add(matInfo.CreateTextureUsageInformation("_Main2ndDissolveNoiseMask", main2ndUV)); // TODO: isDecurl for MainTex2? } // TODO: Main3rd // Matcap - if (material.GetInt("_UseMatCap") != 0 || animation.IsAnimated("_UseMatCap")) + if (matInfo.GetInteger("_UseMatCap") != 0) { - information.Add(new TextureUsageInformation("_MatCapTex", UVChannel.NonMeshRelated)); - information.Add(new TextureUsageInformation("_MatCapBlendMask", UVChannel.UV0)); // No ScaleOffset + information.Add(matInfo.CreateTextureUsageInformation("_MatCapTex", UVChannel.NonMeshRelated)); + information.Add(matInfo.CreateTextureUsageInformation("_MatCapBlendMask", UVChannel.UV0)); // No ScaleOffset // TODO: more properties like: _MatCapBumpMap } // TODO: Matcap2nd // rim light - if (material.GetInt("_UseRim") != 0 || animation.IsAnimated("_UseRim")) + if (matInfo.GetInteger("_UseRim") != 0) { - information.Add(new TextureUsageInformation("_RimColorTex", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_RimColorTex", uvMain)); } // outline - if (material.GetInt("_UseOutline") != 0 || animation.IsAnimated("_UseOutline")) + if (matInfo.GetInteger("_UseOutline") != 0) { - information.Add(new TextureUsageInformation("_OutlineColorTex", uvMain)); - information.Add(new TextureUsageInformation("_OutlineWidthMask", uvMain)); // ?? + information.Add(matInfo.CreateTextureUsageInformation("_OutlineColorTex", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_OutlineWidthMask", uvMain)); // ?? } // emission - if (material.GetInt("_UseEmission") != 0 || animation.IsAnimated("_UseEmission")) + if (matInfo.GetInteger("_UseEmission") != 0) { - information.Add(new TextureUsageInformation("_EmissionMap", uvMain)); - information.Add(new TextureUsageInformation("_EmissionBlendMask", uvMain)); // ?? + information.Add(matInfo.CreateTextureUsageInformation("_EmissionMap", uvMain)); + information.Add(matInfo.CreateTextureUsageInformation("_EmissionBlendMask", uvMain)); // ?? } // TODO: Many Properties From 0e53101cc8d03f595c8d706f9c1511f92f42f7b2 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 13 Sep 2024 19:24:12 +0900 Subject: [PATCH 21/30] feat(optimize-texture): initial full liltoon description --- .../TraceAndOptimize/OptimizeTexture.cs | 2 + Editor/ShaderKnowledge.cs | 566 +++++++++++++++++- 2 files changed, 535 insertions(+), 33 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index c9d6b5893..77fbc139d 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -432,6 +432,8 @@ public TextureUsageInformationCallback(Material material, List GetValue(propertyName, _material.GetInt, considerAnimation); public override float? GetFloat(string propertyName, bool considerAnimation = true) => GetValue(propertyName, _material.GetFloat, considerAnimation); + + public override Vector4? GetVector(string propertyName, bool considerAnimation = true) => GetValue(propertyName, _material.GetVector, considerAnimation); } [Conditional("AAO_OPTIMIZE_TEXTURE_TRACE_LOG")] diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index 7ebeb470e..b298052e3 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -122,6 +122,14 @@ internal TextureUsageInformationCallback() { } /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. public abstract float? GetFloat(string propertyName, bool considerAnimation = true); + /// + /// Returns the float value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + public abstract Vector4? GetVector(string propertyName, bool considerAnimation = true); + /// /// Creates a new instance. /// @@ -130,6 +138,25 @@ internal TextureUsageInformationCallback() { } /// A new instance. public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, UVChannel uvChannel) => new(materialPropertyName, uvChannel); + + /// + /// Creates a new instance for texture without ScaleOffset (_ST). + /// + /// The name of the texture property in the material. + /// The UV channel for the texture. + /// A new instance. + public TextureUsageInformation CreateTextureUsageInformationNoScaleOffset(string materialPropertyName, + UVChannel uvChannel) => new(materialPropertyName, uvChannel); + + /// + /// Creates a new instance. + /// + /// The name of the texture property in the material. + /// The UV channel for the texture. + /// The name of vector4 property for UV Scale and Offset + /// A new instance. + public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, + UVChannel uvChannel, string scaleOffsetProperty) => new(materialPropertyName, uvChannel); } public enum UVChannel @@ -177,71 +204,544 @@ internal TextureUsageInformation(string materialPropertyName, UVChannel uvChanne private static TextureUsageInformation[]? GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) { + /* + * + * [NoScaleOffset] _DitherTex ("Dither", 2D) = "white" {} + * [MainTexture] _MainTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _MainGradationTex ("Gradation Map", 2D) = "white" {} + * [NoScaleOffset] _MainColorAdjustMask ("Adjust Mask", 2D) = "white" {} + * _Main2ndTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _Main2ndBlendMask ("Mask", 2D) = "white" {} + * _Main2ndDissolveMask ("Dissolve Mask", 2D) = "white" {} + * _Main2ndDissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} + * _Main3rdTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _Main3rdBlendMask ("Mask", 2D) = "white" {} + * _Main3rdDissolveMask ("Dissolve Mask", 2D) = "white" {} + * _Main3rdDissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} + * _AlphaMask ("AlphaMask", 2D) = "white" {} + * [Normal] _BumpMap ("Normal Map", 2D) = "bump" {} + * [Normal] _Bump2ndMap ("Normal Map", 2D) = "bump" {} + * [NoScaleOffset] _Bump2ndScaleMask ("Mask", 2D) = "white" {} + * [Normal] _AnisotropyTangentMap ("Tangent Map", 2D) = "bump" {} + * [NoScaleOffset] _AnisotropyScaleMask ("Scale Mask", 2D) = "white" {} + * _AnisotropyShiftNoiseMask ("sNoise", 2D) = "white" {} + * [NoScaleOffset] _BacklightColorTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _ShadowStrengthMask ("sStrength", 2D) = "white" {} + * [NoScaleOffset] _ShadowBorderMask ("sBorder", 2D) = "white" {} + * [NoScaleOffset] _ShadowBlurMask ("sBlur", 2D) = "white" {} + * [NoScaleOffset] _ShadowColorTex ("Shadow Color", 2D) = "black" {} + * [NoScaleOffset] _Shadow2ndColorTex ("2nd Color", 2D) = "black" {} + * [NoScaleOffset] _Shadow3rdColorTex ("3rd Color", 2D) = "black" {} + * [NoScaleOffset] _RimShadeMask ("Mask", 2D) = "white" {} + * [NoScaleOffset] _SmoothnessTex ("Smoothness", 2D) = "white" {} + * [NoScaleOffset] _MetallicGlossMap ("Metallic", 2D) = "white" {} + * [NoScaleOffset] _ReflectionColorTex ("sColor", 2D) = "white" {} + * _MatCapTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _MatCapBlendMask ("Mask", 2D) = "white" {} + * [Normal] _MatCapBumpMap ("Normal Map", 2D) = "bump" {} + * _MatCap2ndTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _MatCap2ndBlendMask ("Mask", 2D) = "white" {} + * [Normal] _MatCap2ndBumpMap ("Normal Map", 2D) = "bump" {} + * [NoScaleOffset] _RimColorTex ("Texture", 2D) = "white" {} + * _GlitterColorTex ("Texture", 2D) = "white" {} + * _GlitterShapeTex ("Texture", 2D) = "white" {} + * _EmissionMap ("Texture", 2D) = "white" {} + * _EmissionBlendMask ("Mask", 2D) = "white" {} + * [NoScaleOffset] _EmissionGradTex ("Gradation Texture", 2D) = "white" {} + * _Emission2ndMap ("Texture", 2D) = "white" {} + * _Emission2ndBlendMask ("Mask", 2D) = "white" {} + * [NoScaleOffset] _Emission2ndGradTex ("Gradation Texture", 2D) = "white" {} + * [NoScaleOffset] _ParallaxMap ("Parallax Map", 2D) = "gray" {} + * _AudioLinkMask ("Mask", 2D) = "blue" {} + * [NoScaleOffset] _AudioLinkLocalMap ("Local Map", 2D) = "black" {} + * _DissolveMask ("Dissolve Mask", 2D) = "white" {} + * _DissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} + * _OutlineTex ("Texture", 2D) = "white" {} + * [NoScaleOffset] _OutlineWidthMask ("Width", 2D) = "white" {} + * [NoScaleOffset][Normal] _OutlineVectorTex ("Vector", 2D) = "bump" {} + [HideInInspector] _BaseMap ("Texture", 2D) = "white" {} + [HideInInspector] _BaseColorMap ("Texture", 2D) = "white" {} + */ + + // memo: + // LIL_SAMPLE_2D just samples from specified texture, sampler and uv + + const string lil_sampler_linear_repeat = "builtin-sampler-Linear-Repeat"; + + TextureUsageInformation LIL_SAMPLE_1D(string textureName, string samplerName, UVChannel uvChannel) + { + return matInfo.CreateTextureUsageInformationNoScaleOffset(textureName, uvChannel); + } + + TextureUsageInformation LIL_SAMPLE_2D(string textureName, string samplerName, UVChannel uvChannel) + { + // might be _LOD: using SampleLevel + return matInfo.CreateTextureUsageInformationNoScaleOffset(textureName, uvChannel); + } + + TextureUsageInformation LIL_SAMPLE_2D_ST(string textureName, string samplerName, UVChannel uvChannel) + { + return matInfo.CreateTextureUsageInformation(textureName, uvChannel, $"{textureName}_ST"); + } + + TextureUsageInformation LIL_SAMPLE_2D_WithST(string textureName, string samplerName, UVChannel uvChannel, string st) + { + return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); + } + // This implementation is made for my Anon + Wahuku for testing this feature. // TODO: version check var information = new List(); var uvMain = UVChannel.UV0; + var uvMainScaleOffset = "_MainTex_ST"; + + TextureUsageInformation UVMain_LIL_SAMPLE_2D_ST(string textureName, string sampler) + { + // TODO: double ST support + // TODO: recheck mainUV settings. includes (not limited to) ScrollRotate, Angle, Tilting + return matInfo.CreateTextureUsageInformation(textureName, uvMain, uvMainScaleOffset); + } + + TextureUsageInformation UVMain_LIL_SAMPLE_2D(string textureName, string sampler) + { + // TODO: recheck mainUV settings. includes (not limited to) ScrollRotate, Angle, Tilting + return matInfo.CreateTextureUsageInformation(textureName, uvMain, uvMainScaleOffset); + } + + TextureUsageInformation LIL_GET_SUBTEX(string textureName, UVChannel uvChannel) + { + var st = $"{textureName}_ST"; - // TODO: UV Animation, angle (_MainTex_ScrollRotate) , Tilting, Offsets (_ST) for MainTex - information.Add(matInfo.CreateTextureUsageInformation("_DitherTex", UVChannel.NonMeshRelated)); - information.Add(matInfo.CreateTextureUsageInformation("_MainTex", uvMain)); - information.Add(matInfo.CreateTextureUsageInformation("_MainColorAdjustMask", uvMain)); - information.Add(matInfo.CreateTextureUsageInformation("_MainGradationTex", uvMain)); + // TODO: consider the following properties + var scrollRotate = $"{textureName}_ScrollRotate"; + var angle = $"{textureName}Angle"; + var isDecal = $"{textureName}IsDecal"; + var isLeftOnly = $"{textureName}IsLeftOnly"; + var isRightOnly = $"{textureName}IsRightOnly"; + var shouldCopy = $"{textureName}ShouldCopy"; + var shouldFlipMirror = $"{textureName}ShouldFlipMirror"; + var shouldFlipCopy = $"{textureName}ShouldFlipCopy"; + var isMSDF = $"{textureName}IsMSDF"; + var decalAnimation = $"{textureName}DecalAnimation"; + var decalSubParam = $"{textureName}DecalSubParam"; + // fd.nv? + // fd.isRightHand? + + return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); + } + + TextureUsageInformation LIL_GET_EMITEX(string textureName, UVChannel uvChannel) + { + var st = $"{textureName}_ST"; + + // TODO: consider the following properties + var scrollRotate = $"{textureName}_ScrollRotate"; + + return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); + } + + TextureUsageInformation LIL_GET_EMIMASK(string textureName, UVChannel uvChannel) + { + var st = $"{textureName}_ST"; + + // sampler is sampler_MainTex + // TODO: consider the following properties + var scrollRotate = $"{textureName}_ScrollRotate"; + + return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); + } + + TextureUsageInformation UVMain_LIL_GET_EMIMASK(string textureName) + { + var st = $"{textureName}_ST"; + + // sampler is sampler_MainTex + // TODO: consider the following properties + var scrollRotate = $"{textureName}_ScrollRotate"; + + return matInfo.CreateTextureUsageInformation(textureName, uvMain, st); + } + + void lilCalcDissolveWithOrWithoutNoise( + // alpha, + // dissolveAlpha, + UVChannel uv, // ? + // positionOS, + // dissolveParams, + // dissolvePos, + string dissolveMask, + string dissolveMaskST, + // dissolveMaskEnabled + string dissolveNoiseMask, + string dissolveNoiseMaskST, + string dissolveNoiseMaskScrollRotate, + // dissolveNoiseStrength, + string samp + ) + { + information.Add(LIL_SAMPLE_2D_WithST(dissolveMask, samp, uv, dissolveMaskST)); + information.Add(LIL_SAMPLE_2D_WithST(dissolveNoiseMask, samp, uv, dissolveNoiseMaskST)); + // TODO: dissolveNoiseMaskScrollRotate + } + + information.Add(matInfo.CreateTextureUsageInformationNoScaleOffset("_DitherTex", UVChannel.NonMeshRelated)); // dither UV is based on screen space + + // TODO: _MainTex with POM / PARALLAX (using LIL_SAMPLE_2D_POM) + information.Add(UVMain_LIL_SAMPLE_2D("_MainTex", "_MainTex")); // main texture + information.Add(matInfo.CreateTextureUsageInformation("_MainGradationTex", UVChannel.NonMeshRelated)); // GradationMap UV is based on color + information.Add(UVMain_LIL_SAMPLE_2D("_MainColorAdjustMask", "_MainTex")); // simple LIL_SAMPLE_2D if (matInfo.GetInteger("_UseMain2ndTex") != 0) { - UVChannel main2ndUV; + // caller of lilGetMain2nd will pass sampler for _MainTex as samp + var samp = "_MainTex"; + + UVChannel uv2nd; + switch (matInfo.GetInteger("_Main2ndTex_UVMode")) + { + case 0: uv2nd = UVChannel.UV0; break; + case 1: uv2nd = UVChannel.UV1; break; + case 2: uv2nd = UVChannel.UV2; break; + case 3: uv2nd = UVChannel.UV3; break; + case 4: uv2nd = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) + default: uv2nd = UVChannel.Unknown; break; + } + information.Add(LIL_GET_SUBTEX("_Main2ndTex", uv2nd)); + information.Add(UVMain_LIL_SAMPLE_2D("_Main2ndBlendMask", samp)); + lilCalcDissolveWithOrWithoutNoise( + UVChannel.UV0, + "_Main2ndDissolveMask", + "_Main2ndDissolveMask_ST", + "_Main2ndDissolveNoiseMask", + "_Main2ndDissolveNoiseMask_ST", + "_Main2ndDissolveNoiseMask_ScrollRotate", + samp + ); + } + + if (matInfo.GetInteger("_UseMain3rdTex") != 0) + { + // caller of lilGetMain3rd will pass sampler for _MainTex as samp + var samp = "_MainTex"; + + UVChannel uv3rd; switch (matInfo.GetInteger("_Main2ndTex_UVMode")) { - case 0: main2ndUV = UVChannel.UV0; break; - case 1: main2ndUV = UVChannel.UV1; break; - case 2: main2ndUV = UVChannel.UV2; break; - case 3: main2ndUV = UVChannel.UV3; break; - case 4: main2ndUV = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) - default: main2ndUV = UVChannel.Unknown; break; + case 0: uv3rd = UVChannel.UV0; break; + case 1: uv3rd = UVChannel.UV1; break; + case 2: uv3rd = UVChannel.UV2; break; + case 3: uv3rd = UVChannel.UV3; break; + case 4: uv3rd = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) + default: uv3rd = UVChannel.Unknown; break; + } + + information.Add(LIL_GET_SUBTEX("_Main3rdTex", uv3rd)); + information.Add(UVMain_LIL_SAMPLE_2D("_Main3rdBlendMask", samp)); + lilCalcDissolveWithOrWithoutNoise( + UVChannel.UV0, + "_Main3rdDissolveMask", + "_Main3rdDissolveMask_ST", + "_Main3rdDissolveNoiseMask", + "_Main3rdDissolveNoiseMask_ST", + "_Main3rdDissolveNoiseMask_ScrollRotate", + samp + ); + } + + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AlphaMask", "_MainTex")); + if (matInfo.GetInteger("_UseBumpMap") != 0) + { + information.Add(UVMain_LIL_SAMPLE_2D_ST("_BumpMap", "_MainTex")); + } + + if (matInfo.GetInteger("_UseBump2ndMap") != 0) + { + var uvBump2nd = UVChannel.UV0; + + switch (matInfo.GetInteger("_Bump2ndMap_UVMode")) + { + case 0: uvBump2nd = UVChannel.UV0; break; + case 1: uvBump2nd = UVChannel.UV1; break; + case 2: uvBump2nd = UVChannel.UV2; break; + case 3: uvBump2nd = UVChannel.UV3; break; + case null: uvBump2nd = UVChannel.Unknown; break; } - information.Add(matInfo.CreateTextureUsageInformation("_Main2ndTex", main2ndUV)); - // TODO: UV Animation, angle (_MainTex2_ScrollRotate) , Tilting, Offsets (_ST) for MainTex2 - information.Add(matInfo.CreateTextureUsageInformation("_Main2ndBlendMask", main2ndUV)); // NO ScaleOffset - information.Add(matInfo.CreateTextureUsageInformation("_Main2ndDissolveMask", main2ndUV)); - information.Add(matInfo.CreateTextureUsageInformation("_Main2ndDissolveNoiseMask", main2ndUV)); - // TODO: isDecurl for MainTex2? + + information.Add(LIL_SAMPLE_2D_ST("_Bump2ndMap", lil_sampler_linear_repeat, uvBump2nd)); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_Bump2ndScaleMask", "_MainTex")); // _Bump2ndScaleMask is defined as NoScaleOffset but sampled with LIL_SAMPLE_2D_ST? } - // TODO: Main3rd + if (matInfo.GetInteger("_UseAnisotropy") != 0) + { + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyTangentMap", "_MainTex")); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyScaleMask", "_MainTex")); + + // _AnisotropyShiftNoiseMask is used in another place but under _UseAnisotropy condition + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyShiftNoiseMask", "_MainTex")); + } + + if (matInfo.GetInteger("_UseBacklight") != 0) + { + var samp = "_MainTex"; + information.Add(UVMain_LIL_SAMPLE_2D_ST("_BacklightColorTex", samp)); + } + + if (matInfo.GetInteger("_UseShadow") != 0) + { + // TODO: Those sampling are GRAD + var samp = "_MainTex"; + information.Add(UVMain_LIL_SAMPLE_2D("_ShadowStrengthMask", lil_sampler_linear_repeat)); + information.Add(UVMain_LIL_SAMPLE_2D("_ShadowBorderMask", lil_sampler_linear_repeat)); + information.Add(UVMain_LIL_SAMPLE_2D("_ShadowBlurMask", lil_sampler_linear_repeat)); + // lilSampleLUT + switch (matInfo.GetInteger("_ShadowColorType")) + { + case 1: + information.Add(matInfo.CreateTextureUsageInformation("_ShadowColorTex", UVChannel.NonMeshRelated)); + information.Add(matInfo.CreateTextureUsageInformation("_Shadow2ndColorTex", UVChannel.NonMeshRelated)); + information.Add(matInfo.CreateTextureUsageInformation("_Shadow3rdColorTex", UVChannel.NonMeshRelated)); + break; + case null: + information.Add(matInfo.CreateTextureUsageInformation("_ShadowColorTex", UVChannel.Unknown)); + information.Add(matInfo.CreateTextureUsageInformation("_Shadow2ndColorTex", UVChannel.Unknown)); + information.Add(matInfo.CreateTextureUsageInformation("_Shadow3rdColorTex", UVChannel.Unknown)); + break; + default: + information.Add(UVMain_LIL_SAMPLE_2D("_ShadowColorTex", samp)); + information.Add(UVMain_LIL_SAMPLE_2D("_Shadow2ndColorTex", samp)); + information.Add(UVMain_LIL_SAMPLE_2D("_Shadow3rdColorTex", samp)); + break; + } + } + + if (matInfo.GetInteger("_UseRimShade") != 0) + { + var samp = "_MainTex"; + + information.Add(UVMain_LIL_SAMPLE_2D("_RimShadeMask", samp)); + } + + if (matInfo.GetInteger("_UseReflection") != 0) + { + // TODO: research + var samp = "_MainTex"; // or lil_sampler_linear_repeat in lil_pass_foreward_reblur.hlsl + + information.Add(UVMain_LIL_SAMPLE_2D_ST("_SmoothnessTex", samp)); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_MetallicGlossMap", samp)); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_ReflectionColorTex", samp)); + } // Matcap if (matInfo.GetInteger("_UseMatCap") != 0) { - information.Add(matInfo.CreateTextureUsageInformation("_MatCapTex", UVChannel.NonMeshRelated)); - information.Add(matInfo.CreateTextureUsageInformation("_MatCapBlendMask", UVChannel.UV0)); // No ScaleOffset - // TODO: more properties like: _MatCapBumpMap + var samp = "_MainTex"; // caller of lilGetMatCap + + information.Add(LIL_SAMPLE_2D("_MatCapTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCapBlendMask", samp)); + + if (matInfo.GetInteger("_MatCapCustomNormal") != 0) + { + information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCapBumpMap", samp)); + } } + + if (matInfo.GetInteger("_UseMatCap2nd") != 0) + { + var samp = "_MainTex"; // caller of lilGetMatCap + + information.Add(LIL_SAMPLE_2D("_MatCap2ndTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCap2ndBlendMask", samp)); - // TODO: Matcap2nd + if (matInfo.GetInteger("_MatCap2ndCustomNormal") != 0) + { + information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCap2ndBumpMap", samp)); + } + } // rim light if (matInfo.GetInteger("_UseRim") != 0) { - information.Add(matInfo.CreateTextureUsageInformation("_RimColorTex", uvMain)); + var samp = "_MainTex"; // caller of lilGetRim + information.Add(UVMain_LIL_SAMPLE_2D_ST("_RimColorTex", samp)); } - // outline - if (matInfo.GetInteger("_UseOutline") != 0) + if (matInfo.GetInteger("_UseGlitter") != 0) { - information.Add(matInfo.CreateTextureUsageInformation("_OutlineColorTex", uvMain)); - information.Add(matInfo.CreateTextureUsageInformation("_OutlineWidthMask", uvMain)); // ?? - } + var samp = "_MainTex"; // caller of lilGetGlitter - // emission + information.Add(UVMain_LIL_SAMPLE_2D_ST("_GlitterColorTex", samp)); + // complex uv + information.Add(matInfo.CreateTextureUsageInformation("_GlitterShapeTex", UVChannel.Unknown)); + } + if (matInfo.GetInteger("_UseEmission") != 0) { - information.Add(matInfo.CreateTextureUsageInformation("_EmissionMap", uvMain)); - information.Add(matInfo.CreateTextureUsageInformation("_EmissionBlendMask", uvMain)); // ?? + var emissionUV = UVChannel.UV0; + + switch (matInfo.GetInteger("_EmissionMap_UVMode")) + { + case 1: emissionUV = UVChannel.UV1; break; + case 2: emissionUV = UVChannel.UV2; break; + case 3: emissionUV = UVChannel.UV3; break; + case 4: emissionUV = UVChannel.NonMeshRelated; break; // uvRim; TODO: check + case null: emissionUV = UVChannel.Unknown; break; + } + + UVChannel _EmissionMapParaTex; + if (matInfo.GetFloat("_EmissionParallaxDepth") == 0) + _EmissionMapParaTex = emissionUV; + else + _EmissionMapParaTex = UVChannel.Unknown; // hard to determine + + // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST + information.Add(LIL_GET_EMITEX("_EmissionMap", _EmissionMapParaTex)); + + // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. + var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); + + if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) + { + information.Add(LIL_GET_EMIMASK("_EmissionBlendMask", UVChannel.UV0)); + } + else + { + information.Add(UVMain_LIL_GET_EMIMASK("_EmissionBlendMask")); + } + + if (matInfo.GetInteger("_EmissionUseGrad") != 0) + { + information.Add(LIL_SAMPLE_1D("_EmissionGradTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + } + } + + if (matInfo.GetInteger("_Emission2ndMap") != 0) + { + var emission2ndUV = UVChannel.UV0; + + switch (matInfo.GetInteger("_Emission2ndMap_UVMode")) + { + case 1: emission2ndUV = UVChannel.UV1; break; + case 2: emission2ndUV = UVChannel.UV2; break; + case 3: emission2ndUV = UVChannel.UV3; break; + case 4: emission2ndUV = UVChannel.NonMeshRelated; break; // uvRim; TODO: check + case null: emission2ndUV = UVChannel.Unknown; break; + } + + UVChannel _Emission2ndMapParaTex; + if (matInfo.GetFloat("_Emission2ndParallaxDepth") == 0) + _Emission2ndMapParaTex = emission2ndUV; + else + _Emission2ndMapParaTex = UVChannel.Unknown; // hard to determine + + // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST + information.Add(LIL_GET_EMITEX("_Emission2ndMap", _Emission2ndMapParaTex)); + + // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. (weird) + // https://github.com/lilxyzw/lilToon/blob/b96470d3dd9092b840052578048b2307fe6d8786/Assets/lilToon/Shader/Includes/lil_common_frag.hlsl#L1819-L1821 + var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); + + if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) + { + information.Add(LIL_GET_EMIMASK("_Emission2ndBlendMask", UVChannel.UV0)); + } + else + { + information.Add(UVMain_LIL_GET_EMIMASK("_Emission2ndBlendMask")); + } + + if (matInfo.GetInteger("_Emission2ndUseGrad") != 0) + { + information.Add(LIL_SAMPLE_1D("_Emission2ndGradTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + } + } + + if (matInfo.GetInteger("_UseParallax") != 0) + { + information.Add(matInfo.CreateTextureUsageInformation("_ParallaxMap", UVChannel.Unknown)); + } + + if (matInfo.GetInteger("_UseAudioLink") != 0 && matInfo.GetInteger("_AudioLink2Vertex") != 0) + { + var _AudioLinkUVMode = matInfo.GetInteger("_AudioLinkUVMode"); + + if (_AudioLinkUVMode is 3 or 4 or null) + { + // TODO: _AudioLinkMask_ScrollRotate + switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) + { + case 0: + default: + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask")); + information.Add(UVMain_LIL_SAMPLE_2D_ST("_AudioLinkMask", lil_sampler_linear_repeat)); + break; + case 1: + information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV1)); + break; + case 2: + information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV2)); + break; + case 3: + information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV3)); + break; + case null: + information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.Unknown)); + break; + } + + information.Add(LIL_SAMPLE_2D("_AudioLinkMask", lil_sampler_linear_repeat, UVChannel.Unknown)); + } + } + + if (matInfo.GetVector("_DissolveParams")?.x != 0) + { + lilCalcDissolveWithOrWithoutNoise( + //fd.col.a, + //dissolveAlpha, + UVChannel.UV0, + //fd.positionOS, + //_DissolveParams, + //_DissolvePos, + "_DissolveMask", + "_DissolveMask_ST", + //_DissolveMaskEnabled, + "_DissolveNoiseMask", + "_DissolveNoiseMask_ST", + "_DissolveNoiseMask_ScrollRotate", + //_DissolveNoiseStrength + "_MainTex" + ); } - // TODO: Many Properties + if (matInfo.GetInteger("_UseOutline") != 0) { // not on material side, on editor side toggle + information.Add(UVMain_LIL_SAMPLE_2D("_OutlineTex", "_OutlineTex")); + information.Add(UVMain_LIL_SAMPLE_2D("_OutlineWidthMask", lil_sampler_linear_repeat)); + // _OutlineVectorTex lil_sampler_linear_repeat + // UVs _OutlineVectorUVMode main,1,2,3 + + switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) + { + case 0: + information.Add(UVMain_LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat)); + break; + case 1: + information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV1)); + break; + case 2: + information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV2)); + break; + case 3: + information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV3)); + break; + default: + case null: + information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.Unknown)); + break; + } + } + + // _BaseMap and _BaseColorMap are unused + return information.ToArray(); } } From 6264d1783ef3a18e173f0f502a1fa2693a04e6da Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 14 Sep 2024 15:47:08 +0900 Subject: [PATCH 22/30] feat(optimize-texture): (WIP) new API for shader information --- .../TraceAndOptimize/OptimizeTexture.cs | 151 ++++++++++++------ Editor/ShaderKnowledge.cs | 126 ++++++++++++++- 2 files changed, 224 insertions(+), 53 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 77fbc139d..2cca52c06 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -34,36 +34,35 @@ internal struct OptimizeTextureImpl { { public readonly MeshInfo2? MeshInfo2; public readonly int SubMeshIndex; - public readonly ShaderKnowledge.UVChannel UVChannel; + public readonly UVChannel UVChannel; public SubMeshId SubMeshId => MeshInfo2 != null ? new SubMeshId(MeshInfo2!, SubMeshIndex) : throw new InvalidOperationException(); - public UVID(SubMeshId subMeshId, ShaderKnowledge.UVChannel uvChannel) + public UVID(SubMeshId subMeshId, UVChannel uvChannel) : this(subMeshId.MeshInfo2, subMeshId.SubMeshIndex, uvChannel) { } - public UVID(MeshInfo2 meshInfo2, int subMeshIndex, ShaderKnowledge.UVChannel uvChannel) + public UVID(MeshInfo2 meshInfo2, int subMeshIndex, UVChannel uvChannel) { MeshInfo2 = meshInfo2; SubMeshIndex = subMeshIndex; UVChannel = uvChannel; switch (uvChannel) { - case ShaderKnowledge.UVChannel.UV0: - case ShaderKnowledge.UVChannel.UV1: - case ShaderKnowledge.UVChannel.UV2: - case ShaderKnowledge.UVChannel.UV3: - case ShaderKnowledge.UVChannel.UV4: - case ShaderKnowledge.UVChannel.UV5: - case ShaderKnowledge.UVChannel.UV6: - case ShaderKnowledge.UVChannel.UV7: + case UVChannel.UV0: + case UVChannel.UV1: + case UVChannel.UV2: + case UVChannel.UV3: + case UVChannel.UV4: + case UVChannel.UV5: + case UVChannel.UV6: + case UVChannel.UV7: break; - case ShaderKnowledge.UVChannel.NonMeshRelated: + case UVChannel.NonMeshRelated: MeshInfo2 = null; SubMeshIndex = -1; break; - case ShaderKnowledge.UVChannel.Unknown: default: throw new ArgumentOutOfRangeException(nameof(uvChannel), uvChannel, null); } @@ -162,7 +161,7 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) } // collect usageInformation for each material, and add to unmergeableMaterials if it's impossible - var usageInformations = new Dictionary(); + var usageInformations = new Dictionary(); { foreach (var (material, _) in materialUsers) @@ -171,10 +170,10 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) material, materialUsers[material].Select(x => context.GetAnimationComponent(x.MeshInfo2.SourceRenderer)) .ToList()); - if (GetTextureUsageInformations(provider) is not { } textureUsageInformations) - unmergeableMaterials.Add(material); + if (ShaderKnowledge.GetTextureUsageInformationForMaterial(provider) && provider.TextureUsageInformations is {} informations) + usageInformations.Add(material, informations.ToArray()); else - usageInformations.Add(material, textureUsageInformations); + unmergeableMaterials.Add(material); } } @@ -377,41 +376,13 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) return (safeToMerge: true, Array.Empty()); } - static ShaderKnowledge.TextureUsageInformation[]? - GetTextureUsageInformations(ShaderKnowledge.TextureUsageInformationCallback callback) - { - if (ShaderKnowledge.GetTextureUsageInformationForMaterial(callback) - is not { } textureInformations) - return null; - - foreach (var textureInformation in textureInformations) - { - switch (textureInformation.UVChannel) - { - case ShaderKnowledge.UVChannel.UV0: - case ShaderKnowledge.UVChannel.UV1: - case ShaderKnowledge.UVChannel.UV2: - case ShaderKnowledge.UVChannel.UV3: - case ShaderKnowledge.UVChannel.UV4: - case ShaderKnowledge.UVChannel.UV5: - case ShaderKnowledge.UVChannel.UV6: - case ShaderKnowledge.UVChannel.UV7: - case ShaderKnowledge.UVChannel.NonMeshRelated: - break; - case ShaderKnowledge.UVChannel.Unknown: - return null; - default: - throw new ArgumentOutOfRangeException(); - } - } - - return textureInformations; - } - class TextureUsageInformationCallback : ShaderKnowledge.TextureUsageInformationCallback { private readonly Material _material; private readonly List> _infos; + private List? _textureUsageInformations = new(); + + public List? TextureUsageInformations => _textureUsageInformations; public TextureUsageInformationCallback(Material material, List> infos) { @@ -434,6 +405,88 @@ public TextureUsageInformationCallback(Material material, List GetValue(propertyName, _material.GetFloat, considerAnimation); public override Vector4? GetVector(string propertyName, bool considerAnimation = true) => GetValue(propertyName, _material.GetVector, considerAnimation); + + public override void RegisterOtherUVUsage(ShaderKnowledge.UsingUVChannels uvChannel) + { + // no longer atlasing is not supported + _textureUsageInformations = null; + } + + public override void RegisterTextureUVUsage( + string textureMaterialPropertyName, + ShaderKnowledge.SamplerStateInformation samplerState, + ShaderKnowledge.UsingUVChannels uvChannels, + UnityEngine.Matrix4x4? uvMatrix) + { + if (_textureUsageInformations == null) return; + if (uvMatrix != Matrix4x4.identity) { + _textureUsageInformations = null; + return; + } + + UVChannel uvChannel; + switch (uvChannels) + { + case ShaderKnowledge.UsingUVChannels.NonMesh: + uvChannel = UVChannel.NonMeshRelated; + break; + case ShaderKnowledge.UsingUVChannels.UV0: + uvChannel = UVChannel.UV0; + break; + case ShaderKnowledge.UsingUVChannels.UV1: + uvChannel = UVChannel.UV1; + break; + case ShaderKnowledge.UsingUVChannels.UV2: + uvChannel = UVChannel.UV2; + break; + case ShaderKnowledge.UsingUVChannels.UV3: + uvChannel = UVChannel.UV3; + break; + case ShaderKnowledge.UsingUVChannels.UV4: + uvChannel = UVChannel.UV4; + break; + case ShaderKnowledge.UsingUVChannels.UV5: + uvChannel = UVChannel.UV5; + break; + case ShaderKnowledge.UsingUVChannels.UV6: + uvChannel = UVChannel.UV6; + break; + case ShaderKnowledge.UsingUVChannels.UV7: + uvChannel = UVChannel.UV7; + break; + default: + _textureUsageInformations = null; + return; + } + + _textureUsageInformations?.Add(new TextureUsageInformation(textureMaterialPropertyName, uvChannel)); + } + } + + private class TextureUsageInformation + { + public string MaterialPropertyName { get; } + public UVChannel UVChannel { get; } + + internal TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) + { + MaterialPropertyName = materialPropertyName; + UVChannel = uvChannel; + } + } + + public enum UVChannel + { + UV0 = 0, + UV1 = 1, + UV2 = 2, + UV3 = 3, + UV4 = 4, + UV5 = 5, + UV6 = 6, + UV7 = 7, + // For example, ScreenSpace (dither) or MatCap + NonMeshRelated = 0x100 + 0, } [Conditional("AAO_OPTIMIZE_TEXTURE_TRACE_LOG")] @@ -463,7 +516,7 @@ public bool IsEmpty() => AtlasResult MayAtlasTexture(ICollection textures, ICollection users) { - if (users.Any(uvid => uvid.UVChannel == ShaderKnowledge.UVChannel.NonMeshRelated)) + if (users.Any(uvid => uvid.UVChannel == UVChannel.NonMeshRelated)) return AtlasResult.Empty; if (CreateIslands(users) is not {} islands) diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index b298052e3..91cf09dd5 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -157,6 +157,124 @@ public TextureUsageInformation CreateTextureUsageInformationNoScaleOffset(string /// A new instance. public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, UVChannel uvChannel, string scaleOffsetProperty) => new(materialPropertyName, uvChannel); + + /// + /// Registers UV Usage that are not considered by Avatar Optimizer. + /// + /// This will the UV Channel not affected by optimizations of Avatar Optimizer. + /// + /// The UVChannels that are used in the shader. + public abstract void RegisterOtherUVUsage(UsingUVChannels uvChannel); + + /// + /// Registers Texture Usage and UV Usage that are considered by Avatar Optimizer. + /// + /// The texture might go to the atlas / UV Packing if the UsingUVChannels is set and the UV Matrix is known + /// + /// The name of the texture property in the material. + /// The information about the sampler state used for the specified texture. + /// The UVChannels that are used in the shader to determine the UV for the texture. + /// The UV Transform Matrix for the texture. This includes textureName_ST scale offset. Null if the UV transfrom is not known. + /// + /// This section describes the current and planned implementation of UV Packing in the Avatar Optimizer about this function. + /// + /// Currently, Avatar Optimizer does UV Packing if (non-exclusive): + /// - Texture is reasonably used by small set of materials + /// - UsingUVChannels is set to only one of UV Channels (per material) + /// - UV Matrix is known and identity matrix + /// + /// However, Avatar Optimizer will support more complex UV Packing in the future: + /// - Support UV Matrix with scale is smaller and rotation is multiple of 90 degrees + /// - multiple UV Channel texture + /// + public abstract void RegisterTextureUVUsage( + string textureMaterialPropertyName, + SamplerStateInformation samplerState, + UsingUVChannels uvChannels, + UnityEngine.Matrix4x4? uvMatrix); + } + + /// + /// The information about the sampler state for the specified texture. + /// + /// You can combine multiple SamplerStateInformation for the texture with `|` operator. + /// + /// You can cast string to SamplerStateInformation to use the sampler state for + /// the specified texture like sampler_MainTex by (SamplerStateInformation)"_MainTex". + /// + /// If your shader is using hardcoded sampler state, you can use the predefined sampler state like + /// or . + /// + internal readonly struct SamplerStateInformation + { + private readonly string _textureName; + private readonly bool _materialProperty; + + public SamplerStateInformation(string textureName) + { + _textureName = textureName; + _materialProperty = true; + } + + // construct builtin non-material property sampler state + private SamplerStateInformation(string textureName, bool dummy) + { + _textureName = textureName; + _materialProperty = false; + } + + // I don't want to expose equals to public API so I made this internal function instead of overriding Equals + internal static bool EQ(SamplerStateInformation left, SamplerStateInformation right) + { + if (left._materialProperty != right._materialProperty) return false; + if (left._textureName != right._textureName) return false; + return true; + } + + public static readonly SamplerStateInformation Unknown = new("Unknown", false); + public static readonly SamplerStateInformation PointClampSampler = new("PointClamp", false); + public static readonly SamplerStateInformation PointRepeatSampler = new("PointRepeat", false); + public static readonly SamplerStateInformation PointMirrorSampler = new("PointMirror", false); + public static readonly SamplerStateInformation PointMirrorOnceSampler = new("PointMirrorOnce", false); + public static readonly SamplerStateInformation LinearClampSampler = new("LinearClamp", false); + public static readonly SamplerStateInformation LinearRepeatSampler = new("LinearRepeat", false); + public static readonly SamplerStateInformation LinearMirrorSampler = new("LinearMirror", false); + public static readonly SamplerStateInformation LinearMirrorOnceSampler = new("LinearMirrorOnce", false); + public static readonly SamplerStateInformation TrilinearClampSampler = new("TrilinearClamp", false); + public static readonly SamplerStateInformation TrilinearRepeatSampler = new("TrilinearRepeat", false); + public static readonly SamplerStateInformation TrilinearMirrorSampler = new("TrilinearMirror", false); + public static readonly SamplerStateInformation TrilinearMirrorOnceSampler = new("TrilinearMirrorOnce", false); + + public static implicit operator SamplerStateInformation(string textureName) => new(textureName); + public static SamplerStateInformation operator|(SamplerStateInformation left, SamplerStateInformation right) => + Combine(left, right); + + private static SamplerStateInformation Combine(SamplerStateInformation left, SamplerStateInformation right) + { + // we may implement better logic in the future + if (EQ(left, right)) return left; + return Unknown; + } + } + + /// + /// The flags to express which UV Channels might be used in the shader. + /// + /// Usage of the UV channels might be specified with some other APIs. + /// + [Flags] + public enum UsingUVChannels + { + NonMesh = 0, + UV0 = 1, + UV1 = 2, + UV2 = 4, + UV3 = 8, + UV4 = 16, + UV5 = 32, + UV6 = 64, + UV7 = 128, + Unknown = 0x7FFFFFFF, } public enum UVChannel @@ -191,7 +309,7 @@ internal TextureUsageInformation(string materialPropertyName, UVChannel uvChanne /// Returns texture usage information for the material. /// /// null if the shader is not supported - public static TextureUsageInformation[]? GetTextureUsageInformationForMaterial(TextureUsageInformationCallback information) + public static bool GetTextureUsageInformationForMaterial(TextureUsageInformationCallback information) { if (AssetDatabase.GetAssetPath(information.Shader).StartsWith("Packages/jp.lilxyzw.liltoon")) { @@ -199,10 +317,10 @@ internal TextureUsageInformation(string materialPropertyName, UVChannel uvChanne return GetTextureUsageInformationForMaterialLiltoon(information); } - return null; + return false; } - private static TextureUsageInformation[]? GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) + private static bool GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) { /* * @@ -742,7 +860,7 @@ string samp // _BaseMap and _BaseColorMap are unused - return information.ToArray(); + return true; } } } From 25a47ea735dd63fc17598a90ca84e57d61a12714 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 14 Sep 2024 21:05:24 +0900 Subject: [PATCH 23/30] feat(shader-knowledge): reimplement with new API --- Editor/ShaderKnowledge.cs | 641 +++++++++++++++++++++----------------- 1 file changed, 350 insertions(+), 291 deletions(-) diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index 91cf09dd5..96f463bdc 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -265,7 +265,6 @@ private static SamplerStateInformation Combine(SamplerStateInformation left, Sam [Flags] public enum UsingUVChannels { - NonMesh = 0, UV0 = 1, UV1 = 2, UV2 = 4, @@ -274,6 +273,7 @@ public enum UsingUVChannels UV5 = 32, UV6 = 64, UV7 = 128, + NonMesh = 256, Unknown = 0x7FFFFFFF, } @@ -322,213 +322,46 @@ public static bool GetTextureUsageInformationForMaterial(TextureUsageInformation private static bool GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) { - /* - * - * [NoScaleOffset] _DitherTex ("Dither", 2D) = "white" {} - * [MainTexture] _MainTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _MainGradationTex ("Gradation Map", 2D) = "white" {} - * [NoScaleOffset] _MainColorAdjustMask ("Adjust Mask", 2D) = "white" {} - * _Main2ndTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _Main2ndBlendMask ("Mask", 2D) = "white" {} - * _Main2ndDissolveMask ("Dissolve Mask", 2D) = "white" {} - * _Main2ndDissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} - * _Main3rdTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _Main3rdBlendMask ("Mask", 2D) = "white" {} - * _Main3rdDissolveMask ("Dissolve Mask", 2D) = "white" {} - * _Main3rdDissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} - * _AlphaMask ("AlphaMask", 2D) = "white" {} - * [Normal] _BumpMap ("Normal Map", 2D) = "bump" {} - * [Normal] _Bump2ndMap ("Normal Map", 2D) = "bump" {} - * [NoScaleOffset] _Bump2ndScaleMask ("Mask", 2D) = "white" {} - * [Normal] _AnisotropyTangentMap ("Tangent Map", 2D) = "bump" {} - * [NoScaleOffset] _AnisotropyScaleMask ("Scale Mask", 2D) = "white" {} - * _AnisotropyShiftNoiseMask ("sNoise", 2D) = "white" {} - * [NoScaleOffset] _BacklightColorTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _ShadowStrengthMask ("sStrength", 2D) = "white" {} - * [NoScaleOffset] _ShadowBorderMask ("sBorder", 2D) = "white" {} - * [NoScaleOffset] _ShadowBlurMask ("sBlur", 2D) = "white" {} - * [NoScaleOffset] _ShadowColorTex ("Shadow Color", 2D) = "black" {} - * [NoScaleOffset] _Shadow2ndColorTex ("2nd Color", 2D) = "black" {} - * [NoScaleOffset] _Shadow3rdColorTex ("3rd Color", 2D) = "black" {} - * [NoScaleOffset] _RimShadeMask ("Mask", 2D) = "white" {} - * [NoScaleOffset] _SmoothnessTex ("Smoothness", 2D) = "white" {} - * [NoScaleOffset] _MetallicGlossMap ("Metallic", 2D) = "white" {} - * [NoScaleOffset] _ReflectionColorTex ("sColor", 2D) = "white" {} - * _MatCapTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _MatCapBlendMask ("Mask", 2D) = "white" {} - * [Normal] _MatCapBumpMap ("Normal Map", 2D) = "bump" {} - * _MatCap2ndTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _MatCap2ndBlendMask ("Mask", 2D) = "white" {} - * [Normal] _MatCap2ndBumpMap ("Normal Map", 2D) = "bump" {} - * [NoScaleOffset] _RimColorTex ("Texture", 2D) = "white" {} - * _GlitterColorTex ("Texture", 2D) = "white" {} - * _GlitterShapeTex ("Texture", 2D) = "white" {} - * _EmissionMap ("Texture", 2D) = "white" {} - * _EmissionBlendMask ("Mask", 2D) = "white" {} - * [NoScaleOffset] _EmissionGradTex ("Gradation Texture", 2D) = "white" {} - * _Emission2ndMap ("Texture", 2D) = "white" {} - * _Emission2ndBlendMask ("Mask", 2D) = "white" {} - * [NoScaleOffset] _Emission2ndGradTex ("Gradation Texture", 2D) = "white" {} - * [NoScaleOffset] _ParallaxMap ("Parallax Map", 2D) = "gray" {} - * _AudioLinkMask ("Mask", 2D) = "blue" {} - * [NoScaleOffset] _AudioLinkLocalMap ("Local Map", 2D) = "black" {} - * _DissolveMask ("Dissolve Mask", 2D) = "white" {} - * _DissolveNoiseMask ("Dissolve Noise Mask", 2D) = "gray" {} - * _OutlineTex ("Texture", 2D) = "white" {} - * [NoScaleOffset] _OutlineWidthMask ("Width", 2D) = "white" {} - * [NoScaleOffset][Normal] _OutlineVectorTex ("Vector", 2D) = "bump" {} - [HideInInspector] _BaseMap ("Texture", 2D) = "white" {} - [HideInInspector] _BaseColorMap ("Texture", 2D) = "white" {} - */ - - // memo: - // LIL_SAMPLE_2D just samples from specified texture, sampler and uv - - const string lil_sampler_linear_repeat = "builtin-sampler-Linear-Repeat"; - - TextureUsageInformation LIL_SAMPLE_1D(string textureName, string samplerName, UVChannel uvChannel) - { - return matInfo.CreateTextureUsageInformationNoScaleOffset(textureName, uvChannel); - } - - TextureUsageInformation LIL_SAMPLE_2D(string textureName, string samplerName, UVChannel uvChannel) - { - // might be _LOD: using SampleLevel - return matInfo.CreateTextureUsageInformationNoScaleOffset(textureName, uvChannel); - } - - TextureUsageInformation LIL_SAMPLE_2D_ST(string textureName, string samplerName, UVChannel uvChannel) - { - return matInfo.CreateTextureUsageInformation(textureName, uvChannel, $"{textureName}_ST"); - } - - TextureUsageInformation LIL_SAMPLE_2D_WithST(string textureName, string samplerName, UVChannel uvChannel, string st) - { - return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); - } - // This implementation is made for my Anon + Wahuku for testing this feature. // TODO: version check - var information = new List(); - var uvMain = UVChannel.UV0; + var uvMain = UsingUVChannels.UV0; var uvMainScaleOffset = "_MainTex_ST"; + UnityEngine.Matrix4x4? uvMainMatrix = ComputeUVMainMatrix(); - TextureUsageInformation UVMain_LIL_SAMPLE_2D_ST(string textureName, string sampler) - { - // TODO: double ST support - // TODO: recheck mainUV settings. includes (not limited to) ScrollRotate, Angle, Tilting - return matInfo.CreateTextureUsageInformation(textureName, uvMain, uvMainScaleOffset); - } - - TextureUsageInformation UVMain_LIL_SAMPLE_2D(string textureName, string sampler) - { - // TODO: recheck mainUV settings. includes (not limited to) ScrollRotate, Angle, Tilting - return matInfo.CreateTextureUsageInformation(textureName, uvMain, uvMainScaleOffset); - } - - TextureUsageInformation LIL_GET_SUBTEX(string textureName, UVChannel uvChannel) - { - var st = $"{textureName}_ST"; - - // TODO: consider the following properties - var scrollRotate = $"{textureName}_ScrollRotate"; - var angle = $"{textureName}Angle"; - var isDecal = $"{textureName}IsDecal"; - var isLeftOnly = $"{textureName}IsLeftOnly"; - var isRightOnly = $"{textureName}IsRightOnly"; - var shouldCopy = $"{textureName}ShouldCopy"; - var shouldFlipMirror = $"{textureName}ShouldFlipMirror"; - var shouldFlipCopy = $"{textureName}ShouldFlipCopy"; - var isMSDF = $"{textureName}IsMSDF"; - var decalAnimation = $"{textureName}DecalAnimation"; - var decalSubParam = $"{textureName}DecalSubParam"; - // fd.nv? - // fd.isRightHand? - - return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); - } - - TextureUsageInformation LIL_GET_EMITEX(string textureName, UVChannel uvChannel) - { - var st = $"{textureName}_ST"; - - // TODO: consider the following properties - var scrollRotate = $"{textureName}_ScrollRotate"; - - return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); - } - - TextureUsageInformation LIL_GET_EMIMASK(string textureName, UVChannel uvChannel) - { - var st = $"{textureName}_ST"; - - // sampler is sampler_MainTex - // TODO: consider the following properties - var scrollRotate = $"{textureName}_ScrollRotate"; - - return matInfo.CreateTextureUsageInformation(textureName, uvChannel, st); - } - - TextureUsageInformation UVMain_LIL_GET_EMIMASK(string textureName) - { - var st = $"{textureName}_ST"; - - // sampler is sampler_MainTex - // TODO: consider the following properties - var scrollRotate = $"{textureName}_ScrollRotate"; - - return matInfo.CreateTextureUsageInformation(textureName, uvMain, st); - } - - void lilCalcDissolveWithOrWithoutNoise( - // alpha, - // dissolveAlpha, - UVChannel uv, // ? - // positionOS, - // dissolveParams, - // dissolvePos, - string dissolveMask, - string dissolveMaskST, - // dissolveMaskEnabled - string dissolveNoiseMask, - string dissolveNoiseMaskST, - string dissolveNoiseMaskScrollRotate, - // dissolveNoiseStrength, - string samp - ) + UnityEngine.Matrix4x4? ComputeUVMainMatrix() { - information.Add(LIL_SAMPLE_2D_WithST(dissolveMask, samp, uv, dissolveMaskST)); - information.Add(LIL_SAMPLE_2D_WithST(dissolveNoiseMask, samp, uv, dissolveNoiseMaskST)); - // TODO: dissolveNoiseMaskScrollRotate + // _ShiftBackfaceUV + if (matInfo.GetFloat("_ShiftBackfaceUV") != 0) return null; // changed depends on face + return STAndScrollRotateToMatrix("_MainTex_ST", "_MainTex_ScrollRotate"); } - information.Add(matInfo.CreateTextureUsageInformationNoScaleOffset("_DitherTex", UVChannel.NonMeshRelated)); // dither UV is based on screen space + matInfo.RegisterTextureUVUsage("_DitherTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh, null); // dither UV is based on screen space // TODO: _MainTex with POM / PARALLAX (using LIL_SAMPLE_2D_POM) - information.Add(UVMain_LIL_SAMPLE_2D("_MainTex", "_MainTex")); // main texture - information.Add(matInfo.CreateTextureUsageInformation("_MainGradationTex", UVChannel.NonMeshRelated)); // GradationMap UV is based on color - information.Add(UVMain_LIL_SAMPLE_2D("_MainColorAdjustMask", "_MainTex")); // simple LIL_SAMPLE_2D + LIL_SAMPLE_2D_WithMat("_MainTex", "_MainTex", uvMain, uvMainMatrix); // main texture + matInfo.RegisterTextureUVUsage("_MainGradationTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); // GradationMap UV is based on color + LIL_SAMPLE_2D_WithMat("_MainColorAdjustMask", "_MainTex", uvMain, uvMainMatrix); // simple LIL_SAMPLE_2D if (matInfo.GetInteger("_UseMain2ndTex") != 0) { // caller of lilGetMain2nd will pass sampler for _MainTex as samp - var samp = "_MainTex"; + SamplerStateInformation samp = "_MainTex"; - UVChannel uv2nd; + UsingUVChannels uv2nd; switch (matInfo.GetInteger("_Main2ndTex_UVMode")) { - case 0: uv2nd = UVChannel.UV0; break; - case 1: uv2nd = UVChannel.UV1; break; - case 2: uv2nd = UVChannel.UV2; break; - case 3: uv2nd = UVChannel.UV3; break; - case 4: uv2nd = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) - default: uv2nd = UVChannel.Unknown; break; + case 0: uv2nd = UsingUVChannels.UV0; break; + case 1: uv2nd = UsingUVChannels.UV1; break; + case 2: uv2nd = UsingUVChannels.UV2; break; + case 3: uv2nd = UsingUVChannels.UV3; break; + case 4: uv2nd = UsingUVChannels.NonMesh; break; // MatCap (normal-based UV) + default: uv2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; } - information.Add(LIL_GET_SUBTEX("_Main2ndTex", uv2nd)); - information.Add(UVMain_LIL_SAMPLE_2D("_Main2ndBlendMask", samp)); + LIL_GET_SUBTEX("_Main2ndTex", uv2nd); + LIL_SAMPLE_2D_WithMat("_Main2ndBlendMask", samp, uvMain, uvMainMatrix); lilCalcDissolveWithOrWithoutNoise( - UVChannel.UV0, + UsingUVChannels.UV0, "_Main2ndDissolveMask", "_Main2ndDissolveMask_ST", "_Main2ndDissolveNoiseMask", @@ -543,21 +376,21 @@ string samp // caller of lilGetMain3rd will pass sampler for _MainTex as samp var samp = "_MainTex"; - UVChannel uv3rd; + UsingUVChannels uv3rd; switch (matInfo.GetInteger("_Main2ndTex_UVMode")) { - case 0: uv3rd = UVChannel.UV0; break; - case 1: uv3rd = UVChannel.UV1; break; - case 2: uv3rd = UVChannel.UV2; break; - case 3: uv3rd = UVChannel.UV3; break; - case 4: uv3rd = UVChannel.NonMeshRelated; break; // MatCap (normal-based UV) - default: uv3rd = UVChannel.Unknown; break; + case 0: uv3rd = UsingUVChannels.UV0; break; + case 1: uv3rd = UsingUVChannels.UV1; break; + case 2: uv3rd = UsingUVChannels.UV2; break; + case 3: uv3rd = UsingUVChannels.UV3; break; + case 4: uv3rd = UsingUVChannels.NonMesh; break; // MatCap (normal-based UV) + default: uv3rd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; } - information.Add(LIL_GET_SUBTEX("_Main3rdTex", uv3rd)); - information.Add(UVMain_LIL_SAMPLE_2D("_Main3rdBlendMask", samp)); + LIL_GET_SUBTEX("_Main3rdTex", uv3rd); + LIL_SAMPLE_2D_WithMat("_Main3rdBlendMask", samp, uvMain, uvMainMatrix); lilCalcDissolveWithOrWithoutNoise( - UVChannel.UV0, + UsingUVChannels.UV0, "_Main3rdDissolveMask", "_Main3rdDissolveMask_ST", "_Main3rdDissolveNoiseMask", @@ -567,68 +400,70 @@ string samp ); } - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AlphaMask", "_MainTex")); + LIL_SAMPLE_2D_ST_WithMat("_AlphaMask", "_MainTex", uvMain, uvMainMatrix); if (matInfo.GetInteger("_UseBumpMap") != 0) { - information.Add(UVMain_LIL_SAMPLE_2D_ST("_BumpMap", "_MainTex")); + LIL_SAMPLE_2D_ST_WithMat("_BumpMap", "_MainTex", uvMain, uvMainMatrix); } if (matInfo.GetInteger("_UseBump2ndMap") != 0) { - var uvBump2nd = UVChannel.UV0; + var uvBump2nd = UsingUVChannels.UV0; switch (matInfo.GetInteger("_Bump2ndMap_UVMode")) { - case 0: uvBump2nd = UVChannel.UV0; break; - case 1: uvBump2nd = UVChannel.UV1; break; - case 2: uvBump2nd = UVChannel.UV2; break; - case 3: uvBump2nd = UVChannel.UV3; break; - case null: uvBump2nd = UVChannel.Unknown; break; + case 0: uvBump2nd = UsingUVChannels.UV0; break; + case 1: uvBump2nd = UsingUVChannels.UV1; break; + case 2: uvBump2nd = UsingUVChannels.UV2; break; + case 3: uvBump2nd = UsingUVChannels.UV3; break; + case null: uvBump2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; } - information.Add(LIL_SAMPLE_2D_ST("_Bump2ndMap", lil_sampler_linear_repeat, uvBump2nd)); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_Bump2ndScaleMask", "_MainTex")); // _Bump2ndScaleMask is defined as NoScaleOffset but sampled with LIL_SAMPLE_2D_ST? + LIL_SAMPLE_2D_ST("_Bump2ndMap", SamplerStateInformation.LinearRepeatSampler, uvBump2nd); + LIL_SAMPLE_2D_ST_WithMat("_Bump2ndScaleMask", "_MainTex", uvMain, uvMainMatrix); + + // Note: _Bump2ndScaleMask is defined as NoScaleOffset but sampled with LIL_SAMPLE_2D_ST? } if (matInfo.GetInteger("_UseAnisotropy") != 0) { - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyTangentMap", "_MainTex")); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyScaleMask", "_MainTex")); + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyTangentMap", "_MainTex", uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyScaleMask", "_MainTex", uvMain, uvMainMatrix); // _AnisotropyShiftNoiseMask is used in another place but under _UseAnisotropy condition - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AnisotropyShiftNoiseMask", "_MainTex")); + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyShiftNoiseMask", "_MainTex", uvMain, uvMainMatrix); } if (matInfo.GetInteger("_UseBacklight") != 0) { var samp = "_MainTex"; - information.Add(UVMain_LIL_SAMPLE_2D_ST("_BacklightColorTex", samp)); + LIL_SAMPLE_2D_ST_WithMat("_BacklightColorTex", samp, uvMain, uvMainMatrix); } if (matInfo.GetInteger("_UseShadow") != 0) { - // TODO: Those sampling are GRAD - var samp = "_MainTex"; - information.Add(UVMain_LIL_SAMPLE_2D("_ShadowStrengthMask", lil_sampler_linear_repeat)); - information.Add(UVMain_LIL_SAMPLE_2D("_ShadowBorderMask", lil_sampler_linear_repeat)); - information.Add(UVMain_LIL_SAMPLE_2D("_ShadowBlurMask", lil_sampler_linear_repeat)); + SamplerStateInformation samp = "_MainTex"; + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowStrengthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBorderMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBlurMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); // lilSampleLUT switch (matInfo.GetInteger("_ShadowColorType")) { case 1: - information.Add(matInfo.CreateTextureUsageInformation("_ShadowColorTex", UVChannel.NonMeshRelated)); - information.Add(matInfo.CreateTextureUsageInformation("_Shadow2ndColorTex", UVChannel.NonMeshRelated)); - information.Add(matInfo.CreateTextureUsageInformation("_Shadow3rdColorTex", UVChannel.NonMeshRelated)); + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); break; case null: - information.Add(matInfo.CreateTextureUsageInformation("_ShadowColorTex", UVChannel.Unknown)); - information.Add(matInfo.CreateTextureUsageInformation("_Shadow2ndColorTex", UVChannel.Unknown)); - information.Add(matInfo.CreateTextureUsageInformation("_Shadow3rdColorTex", UVChannel.Unknown)); + var sampler = samp | SamplerStateInformation.LinearClampSampler; + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); break; default: - information.Add(UVMain_LIL_SAMPLE_2D("_ShadowColorTex", samp)); - information.Add(UVMain_LIL_SAMPLE_2D("_Shadow2ndColorTex", samp)); - information.Add(UVMain_LIL_SAMPLE_2D("_Shadow3rdColorTex", samp)); + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", samp, uvMain, uvMainMatrix); break; } } @@ -637,17 +472,17 @@ string samp { var samp = "_MainTex"; - information.Add(UVMain_LIL_SAMPLE_2D("_RimShadeMask", samp)); + LIL_SAMPLE_2D_WithMat("_RimShadeMask", samp, uvMain, uvMainMatrix); } if (matInfo.GetInteger("_UseReflection") != 0) { // TODO: research - var samp = "_MainTex"; // or lil_sampler_linear_repeat in lil_pass_foreward_reblur.hlsl + var samp = "_MainTex"; // or SamplerStateInformation.LinearRepeatSampler in lil_pass_foreward_reblur.hlsl - information.Add(UVMain_LIL_SAMPLE_2D_ST("_SmoothnessTex", samp)); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_MetallicGlossMap", samp)); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_ReflectionColorTex", samp)); + LIL_SAMPLE_2D_ST_WithMat("_SmoothnessTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_MetallicGlossMap", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_ReflectionColorTex", samp, uvMain, uvMainMatrix); } // Matcap @@ -655,12 +490,12 @@ string samp { var samp = "_MainTex"; // caller of lilGetMatCap - information.Add(LIL_SAMPLE_2D("_MatCapTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCapBlendMask", samp)); + LIL_SAMPLE_2D("_MatCapTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); + LIL_SAMPLE_2D_ST_WithMat("_MatCapBlendMask", samp, uvMain, uvMainMatrix); if (matInfo.GetInteger("_MatCapCustomNormal") != 0) { - information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCapBumpMap", samp)); + LIL_SAMPLE_2D_ST_WithMat("_MatCapBumpMap", samp, uvMain, uvMainMatrix); } } @@ -668,12 +503,12 @@ string samp { var samp = "_MainTex"; // caller of lilGetMatCap - information.Add(LIL_SAMPLE_2D("_MatCap2ndTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCap2ndBlendMask", samp)); + LIL_SAMPLE_2D("_MatCap2ndTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); + LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBlendMask", samp, uvMain, uvMainMatrix); if (matInfo.GetInteger("_MatCap2ndCustomNormal") != 0) { - information.Add(UVMain_LIL_SAMPLE_2D_ST("_MatCap2ndBumpMap", samp)); + LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBumpMap", samp, uvMain, uvMainMatrix); } } @@ -681,79 +516,74 @@ string samp if (matInfo.GetInteger("_UseRim") != 0) { var samp = "_MainTex"; // caller of lilGetRim - information.Add(UVMain_LIL_SAMPLE_2D_ST("_RimColorTex", samp)); + LIL_SAMPLE_2D_ST_WithMat("_RimColorTex", samp, uvMain, uvMainMatrix); } if (matInfo.GetInteger("_UseGlitter") != 0) { var samp = "_MainTex"; // caller of lilGetGlitter - information.Add(UVMain_LIL_SAMPLE_2D_ST("_GlitterColorTex", samp)); - // complex uv - information.Add(matInfo.CreateTextureUsageInformation("_GlitterShapeTex", UVChannel.Unknown)); + LIL_SAMPLE_2D_ST_WithMat("_GlitterColorTex", samp, uvMain, uvMainMatrix); + if (matInfo.GetInteger("_GlitterApplyShape") != 0) + { + // complex uv + LIL_SAMPLE_2D_GRAD("_GlitterShapeTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh); + } } if (matInfo.GetInteger("_UseEmission") != 0) { - var emissionUV = UVChannel.UV0; + UsingUVChannels emissionUV = UsingUVChannels.UV0; switch (matInfo.GetInteger("_EmissionMap_UVMode")) { - case 1: emissionUV = UVChannel.UV1; break; - case 2: emissionUV = UVChannel.UV2; break; - case 3: emissionUV = UVChannel.UV3; break; - case 4: emissionUV = UVChannel.NonMeshRelated; break; // uvRim; TODO: check - case null: emissionUV = UVChannel.Unknown; break; + case 1: emissionUV = UsingUVChannels.UV1; break; + case 2: emissionUV = UsingUVChannels.UV2; break; + case 3: emissionUV = UsingUVChannels.UV3; break; + case 4: emissionUV = UsingUVChannels.NonMesh; break; // uvRim; TODO: check + case null: emissionUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3 | UsingUVChannels.NonMesh; break; } - UVChannel _EmissionMapParaTex; - if (matInfo.GetFloat("_EmissionParallaxDepth") == 0) - _EmissionMapParaTex = emissionUV; - else - _EmissionMapParaTex = UVChannel.Unknown; // hard to determine + var parallaxEnabled = matInfo.GetFloat("_EmissionParallaxDepth") != 0; - // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST - information.Add(LIL_GET_EMITEX("_EmissionMap", _EmissionMapParaTex)); + LIL_GET_EMITEX("_EmissionMap", emissionUV, parallaxEnabled); // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) { - information.Add(LIL_GET_EMIMASK("_EmissionBlendMask", UVChannel.UV0)); + LIL_GET_EMIMASK("_EmissionBlendMask", UsingUVChannels.UV0); } else { - information.Add(UVMain_LIL_GET_EMIMASK("_EmissionBlendMask")); + LIL_GET_EMIMASK_WithMat("_EmissionBlendMask", uvMain, uvMainMatrix); } if (matInfo.GetInteger("_EmissionUseGrad") != 0) { - information.Add(LIL_SAMPLE_1D("_EmissionGradTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + LIL_SAMPLE_1D("_EmissionGradTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); } } if (matInfo.GetInteger("_Emission2ndMap") != 0) { - var emission2ndUV = UVChannel.UV0; + UsingUVChannels emission2ndUV = UsingUVChannels.UV0; switch (matInfo.GetInteger("_Emission2ndMap_UVMode")) { - case 1: emission2ndUV = UVChannel.UV1; break; - case 2: emission2ndUV = UVChannel.UV2; break; - case 3: emission2ndUV = UVChannel.UV3; break; - case 4: emission2ndUV = UVChannel.NonMeshRelated; break; // uvRim; TODO: check - case null: emission2ndUV = UVChannel.Unknown; break; + case 1: emission2ndUV = UsingUVChannels.UV1; break; + case 2: emission2ndUV = UsingUVChannels.UV2; break; + case 3: emission2ndUV = UsingUVChannels.UV3; break; + case 4: emission2ndUV = UsingUVChannels.NonMesh; break; // uvRim; TODO: check + case null: emission2ndUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3 | UsingUVChannels.NonMesh; break; } - UVChannel _Emission2ndMapParaTex; - if (matInfo.GetFloat("_Emission2ndParallaxDepth") == 0) - _Emission2ndMapParaTex = emission2ndUV; - else - _Emission2ndMapParaTex = UVChannel.Unknown; // hard to determine + var parallaxEnabled = matInfo.GetFloat("_Emission2ndParallaxDepth") != 0; // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST - information.Add(LIL_GET_EMITEX("_Emission2ndMap", _Emission2ndMapParaTex)); + LIL_GET_EMITEX("_Emission2ndMap", emission2ndUV, parallaxEnabled); // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. (weird) // https://github.com/lilxyzw/lilToon/blob/b96470d3dd9092b840052578048b2307fe6d8786/Assets/lilToon/Shader/Includes/lil_common_frag.hlsl#L1819-L1821 @@ -761,22 +591,22 @@ string samp if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) { - information.Add(LIL_GET_EMIMASK("_Emission2ndBlendMask", UVChannel.UV0)); + LIL_GET_EMIMASK("_Emission2ndBlendMask", UsingUVChannels.UV0); } else { - information.Add(UVMain_LIL_GET_EMIMASK("_Emission2ndBlendMask")); + LIL_GET_EMIMASK_WithMat("_Emission2ndBlendMask", uvMain, uvMainMatrix); } if (matInfo.GetInteger("_Emission2ndUseGrad") != 0) { - information.Add(LIL_SAMPLE_1D("_Emission2ndGradTex", lil_sampler_linear_repeat, UVChannel.NonMeshRelated)); + LIL_SAMPLE_1D("_Emission2ndGradTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); } } if (matInfo.GetInteger("_UseParallax") != 0) { - information.Add(matInfo.CreateTextureUsageInformation("_ParallaxMap", UVChannel.Unknown)); + matInfo.RegisterTextureUVUsage("_ParallaxMap", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV0, null); } if (matInfo.GetInteger("_UseAudioLink") != 0 && matInfo.GetInteger("_AudioLink2Vertex") != 0) @@ -786,28 +616,28 @@ string samp if (_AudioLinkUVMode is 3 or 4 or null) { // TODO: _AudioLinkMask_ScrollRotate + var sampler = "_AudioLinkMask" | SamplerStateInformation.LinearRepeatSampler; switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) { case 0: default: - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask")); - information.Add(UVMain_LIL_SAMPLE_2D_ST("_AudioLinkMask", lil_sampler_linear_repeat)); + LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, uvMain, uvMainMatrix); break; case 1: - information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV1)); + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV1); break; case 2: - information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV2)); + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV2); break; case 3: - information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.UV3)); + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV3); break; case null: - information.Add(LIL_SAMPLE_2D_ST("_AudioLinkMask", "_AudioLinkMask", UVChannel.Unknown)); + LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, + uvMain | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, + Combine(uvMainMatrix, Matrix4x4.identity)); break; } - - information.Add(LIL_SAMPLE_2D("_AudioLinkMask", lil_sampler_linear_repeat, UVChannel.Unknown)); } } @@ -816,7 +646,7 @@ string samp lilCalcDissolveWithOrWithoutNoise( //fd.col.a, //dissolveAlpha, - UVChannel.UV0, + UsingUVChannels.UV0, //fd.positionOS, //_DissolveParams, //_DissolvePos, @@ -832,28 +662,33 @@ string samp } if (matInfo.GetInteger("_UseOutline") != 0) { // not on material side, on editor side toggle - information.Add(UVMain_LIL_SAMPLE_2D("_OutlineTex", "_OutlineTex")); - information.Add(UVMain_LIL_SAMPLE_2D("_OutlineWidthMask", lil_sampler_linear_repeat)); - // _OutlineVectorTex lil_sampler_linear_repeat + LIL_SAMPLE_2D_WithMat("_OutlineTex", "_OutlineTex", uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_OutlineWidthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); + // _OutlineVectorTex SamplerStateInformation.LinearRepeatSampler // UVs _OutlineVectorUVMode main,1,2,3 switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) { case 0: - information.Add(UVMain_LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat)); + LIL_SAMPLE_2D_WithMat("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); break; case 1: - information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV1)); + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV1); break; case 2: - information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV2)); + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV2); break; case 3: - information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.UV3)); + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV3); break; default: case null: - information.Add(LIL_SAMPLE_2D("_OutlineVectorTex", lil_sampler_linear_repeat, UVChannel.Unknown)); + matInfo.RegisterTextureUVUsage( + "_OutlineVectorTex", + SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, + Combine(uvMainMatrix, UnityEngine.Matrix4x4.identity) + ); break; } } @@ -861,6 +696,230 @@ string samp // _BaseMap and _BaseColorMap are unused return true; + + void LIL_SAMPLE_1D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + UnityEngine.Matrix4x4.identity + ); + } + + void LIL_SAMPLE_2D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + // might be _LOD: using SampleLevel + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + UnityEngine.Matrix4x4.identity + ); + } + + void LIL_SAMPLE_2D_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + // might be _LOD: using SampleLevel + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + matrix + ); + } + + void LIL_SAMPLE_2D_GRAD(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + // additional parameter for SampleGrad does not affect UV location much + LIL_SAMPLE_2D(textureName, samplerName, uvChannel); + } + + void LIL_SAMPLE_2D_GRAD_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + // additional parameter for SampleGrad does not affect UV location much + LIL_SAMPLE_2D_WithMat(textureName, samplerName, uvChannel, matrix); + } + + void LIL_SAMPLE_2D_ST(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + STToMatrix($"{textureName}_ST") + ); + } + + void LIL_SAMPLE_2D_ST_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + Multiply(STToMatrix($"{textureName}_ST"), matrix) + ); + } + + void LIL_GET_SUBTEX(string textureName, UsingUVChannels uvChannel) + { + // lilGetSubTex + + // TODO: consider the following properties + var st = $"{textureName}_ST"; + var scrollRotate = $"{textureName}_ScrollRotate"; + var angle = $"{textureName}Angle"; + var isDecal = $"{textureName}IsDecal"; + var isLeftOnly = $"{textureName}IsLeftOnly"; + var isRightOnly = $"{textureName}IsRightOnly"; + var shouldCopy = $"{textureName}ShouldCopy"; + var shouldFlipMirror = $"{textureName}ShouldFlipMirror"; + var shouldFlipCopy = $"{textureName}ShouldFlipCopy"; + var isMSDF = $"{textureName}IsMSDF"; + var decalAnimation = $"{textureName}DecalAnimation"; + var decalSubParam = $"{textureName}DecalSubParam"; + // fd.nv? + // fd.isRightHand? + + + Matrix4x4? ComputeMatrix() + { + var stValueOpt = matInfo.GetVector(st); + var rotateValueOpt = matInfo.GetVector(scrollRotate); + var angleValueOpt = matInfo.GetFloat(angle); + //var isDecalValueOpt = matInfo.GetFloat(isDecal); + var isLeftOnlyValueOpt = matInfo.GetFloat(isLeftOnly); + var isRightOnlyValueOpt = matInfo.GetFloat(isRightOnly); + var shouldCopyValueOpt = matInfo.GetFloat(shouldCopy); + var shouldFlipMirrorValueOpt = matInfo.GetFloat(shouldFlipMirror); + var shouldFlipCopyValueOpt = matInfo.GetFloat(shouldFlipCopy); + //var isMSDFValueOpt = matInfo.GetFloat(isMSDF); + var decalAnimationValueOpt = matInfo.GetVector(decalAnimation); + // var decalSubParamValueOpt = matInfo.GetVector(decalSubParam); + + if (stValueOpt is not { } stValue) return null; + if (rotateValueOpt is not { } rotateValue) return null; + if (angleValueOpt is not { } angleValue) return null; + + rotateValue.z = angleValue; + + if (STAndScrollRotateValueToMatrix(stValue, rotateValue) is not { } matrix) return null; + + // shouldCopy is true => x = abs(x - 0.5) + 0.5 + if (shouldCopyValueOpt != 0) return null; + // shouldFlipCopy is true => flips + if (shouldFlipCopyValueOpt != 0) return null; + // shouldFlipMirror is true => flips + if (shouldFlipMirrorValueOpt != 0) return null; + + // isDecal is true => decal + if (isLeftOnlyValueOpt != 0) return null; + if (isRightOnlyValueOpt != 0) return null; + + // rotation is performed in STAndScrollRotateValueToMatrix + + if (decalAnimationValueOpt != new Vector4(1.0f, 1.0f, 1.0f, 30.0f)) return null; + + return matrix; + } + + matInfo.RegisterTextureUVUsage(textureName, textureName, uvChannel, ComputeMatrix()); + } + + void LIL_GET_EMITEX(string textureName, UsingUVChannels uvChannel, bool parallaxEnabled) + { + LIL_SAMPLE_2D_WithMat(textureName, textureName, uvChannel, + parallaxEnabled ? null : STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); + } + + void LIL_GET_EMIMASK_WithMat(string textureName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, + Multiply(matrix, STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate"))); + } + + void LIL_GET_EMIMASK(string textureName, UsingUVChannels uvChannel) + { + LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, + STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); + } + + void lilCalcDissolveWithOrWithoutNoise( + // alpha, + // dissolveAlpha, + UsingUVChannels uv, // ? + // positionOS, + // dissolveParams, + // dissolvePos, + string dissolveMask, + string dissolveMaskST, + // dissolveMaskEnabled + string dissolveNoiseMask, + string dissolveNoiseMaskST, + string dissolveNoiseMaskScrollRotate, + // dissolveNoiseStrength, + SamplerStateInformation samp + ) + { + LIL_SAMPLE_2D_WithMat(dissolveMask, samp, uv, STToMatrix(dissolveMaskST)); + LIL_SAMPLE_2D_WithMat(dissolveNoiseMask, samp, uv, STAndScrollRotateToMatrix(dissolveNoiseMaskST, dissolveNoiseMaskScrollRotate)); + } + + // lilCalcUV + Matrix4x4? STToMatrix(string stPropertyName) => STValueToMatrix(matInfo.GetVector(stPropertyName)); + + Matrix4x4? STValueToMatrix(Vector4? stIn) + { + if (stIn is not { } st) return null; + + var matrix = Matrix4x4.identity; + matrix.m00 = st.x; + matrix.m11 = st.y; + matrix.m03 = st.z; + matrix.m13 = st.w; + + return matrix; + } + + // lilCalcUV + Matrix4x4? STAndScrollRotateToMatrix(string stPropertyName, string scrollRotatePropertyName) => + STAndScrollRotateValueToMatrix(matInfo.GetVector(stPropertyName), matInfo.GetVector(scrollRotatePropertyName)); + + Matrix4x4? STAndScrollRotateValueToMatrix(Vector4? stValueIn, Vector4? scrollRotateIn) + { + if (STValueToMatrix(stValueIn) is not { } stMatrix) return null; + if (scrollRotateIn is not { } scrollRotate) return stMatrix; + + float staticAngle = scrollRotate.z; + float scrollAngleSpeed = scrollRotate.w; + Vector2 scrollSpeed = new(scrollRotate.x, scrollRotate.y); + + if (scrollSpeed != Vector2.zero || scrollAngleSpeed != 0) return null; + + if (staticAngle == 0) return stMatrix; + + var result = stMatrix; + + result *= Matrix4x4.TRS(new Vector3(-0.5f, -0.5f), Quaternion.identity, Vector3.one); + result *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, staticAngle), Vector3.one); + result *= Matrix4x4.TRS(new Vector3(0.5f, 0.5f), Quaternion.identity, Vector3.one); + + return result; + } + + static Matrix4x4? Combine(Matrix4x4? a, Matrix4x4? b) + { + if (a == null) return b; + if (b == null) return a; + if (a == b) return a; + return null; + } + + Matrix4x4? Multiply(Matrix4x4? a, Matrix4x4? b) + { + if (a == null || b == null) return null; + return a.Value * b.Value; + } } } } From 686aa6201b6d19e2ea3291e3ff1aef38561962d8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 14 Sep 2024 21:05:38 +0900 Subject: [PATCH 24/30] fix: bugfixes in optimize texture --- Editor/Processors/TraceAndOptimize/OptimizeTexture.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 2cca52c06..e47fcc0f4 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -182,6 +182,7 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) var materialsByUSerSubmeshId = new Dictionary, HashSet>(); foreach (var (material, users) in materialUsers) { + if (unmergeableMaterials.Contains(material)) continue; var set = new EqualsHashSet(users); if (!materialsByUSerSubmeshId.TryGetValue(set, out var materials)) materialsByUSerSubmeshId.Add(set, materials = new HashSet()); @@ -419,11 +420,6 @@ public override void RegisterTextureUVUsage( UnityEngine.Matrix4x4? uvMatrix) { if (_textureUsageInformations == null) return; - if (uvMatrix != Matrix4x4.identity) { - _textureUsageInformations = null; - return; - } - UVChannel uvChannel; switch (uvChannels) { @@ -459,6 +455,11 @@ public override void RegisterTextureUVUsage( return; } + if (uvMatrix != Matrix4x4.identity && uvChannel != UVChannel.NonMeshRelated) { + _textureUsageInformations = null; + return; + } + _textureUsageInformations?.Add(new TextureUsageInformation(textureMaterialPropertyName, uvChannel)); } } From a794e8aa301ed13c0b9d8992539298db44558a28 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 14 Sep 2024 21:06:55 +0900 Subject: [PATCH 25/30] chore(shader-knowledge): remove traditional API --- Editor/ShaderKnowledge.cs | 56 --------------------------------------- 1 file changed, 56 deletions(-) diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index 96f463bdc..cc96f250f 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -130,34 +130,6 @@ internal TextureUsageInformationCallback() { } /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. public abstract Vector4? GetVector(string propertyName, bool considerAnimation = true); - /// - /// Creates a new instance. - /// - /// The name of the texture property in the material. - /// The UV channel for the texture. - /// A new instance. - public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, - UVChannel uvChannel) => new(materialPropertyName, uvChannel); - - /// - /// Creates a new instance for texture without ScaleOffset (_ST). - /// - /// The name of the texture property in the material. - /// The UV channel for the texture. - /// A new instance. - public TextureUsageInformation CreateTextureUsageInformationNoScaleOffset(string materialPropertyName, - UVChannel uvChannel) => new(materialPropertyName, uvChannel); - - /// - /// Creates a new instance. - /// - /// The name of the texture property in the material. - /// The UV channel for the texture. - /// The name of vector4 property for UV Scale and Offset - /// A new instance. - public TextureUsageInformation CreateTextureUsageInformation(string materialPropertyName, - UVChannel uvChannel, string scaleOffsetProperty) => new(materialPropertyName, uvChannel); - /// /// Registers UV Usage that are not considered by Avatar Optimizer. /// @@ -277,34 +249,6 @@ public enum UsingUVChannels Unknown = 0x7FFFFFFF, } - public enum UVChannel - { - UV0 = 0, - UV1 = 1, - UV2 = 2, - UV3 = 3, - UV4 = 4, - UV5 = 5, - UV6 = 6, - UV7 = 7, - // For example, ScreenSpace (dither) or MatCap - NonMeshRelated = 0x100 + 0, - Unknown = -1, - } - - public class TextureUsageInformation - { - public string MaterialPropertyName { get; } - public UVChannel UVChannel { get; } - - internal TextureUsageInformation(string materialPropertyName, UVChannel uvChannel) - { - MaterialPropertyName = materialPropertyName; - UVChannel = uvChannel; - } - } - - // TODO: define return type /// /// Returns texture usage information for the material. /// From 87395385ab1ba259155cc6f89722f2b84edb8170 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 16 Sep 2024 15:45:25 +0900 Subject: [PATCH 26/30] refactor: split function for future testing --- .../TraceAndOptimize/OptimizeTexture.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index e47fcc0f4..7ef02891c 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -79,7 +79,7 @@ public override string ToString() } } - readonly struct SubMeshId : IEquatable + internal readonly struct SubMeshId : IEquatable { public readonly MeshInfo2 MeshInfo2; public readonly int SubMeshIndex; @@ -101,6 +101,15 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) { if (!state.OptimizeTexture) return; + var materialInformation = CollectMaterials(context); + + DoAtlas(materialInformation); + } + + internal IEnumerable<(Material, TextureUsageInformation[], HashSet)> CollectMaterials( + BuildContext context + ) + { // those two maps should only hold mergeable materials and submeshes var materialUsers = new Dictionary>(); var materialsBySubMesh = new Dictionary>(); @@ -241,12 +250,17 @@ internal void Execute(BuildContext context, TraceAndOptimizeState state) } } + return materialUsers.Select(x => (x.Key, usageInformations[x.Key], x.Value)); + } + + internal void DoAtlas(IEnumerable<(Material, TextureUsageInformation[], HashSet)> materialInformation) + { { var textureUserMaterials = new Dictionary>(); var textureByUVs = new Dictionary, HashSet>(); - foreach (var (material, value) in materialUsers) + foreach (var (material, usageInformations, value) in materialInformation) { - foreach (var information in usageInformations[material]) + foreach (var information in usageInformations) { var texture = (Texture2D)material.GetTexture(information.MaterialPropertyName); if (texture == null) continue; @@ -464,7 +478,7 @@ public override void RegisterTextureUVUsage( } } - private class TextureUsageInformation + internal class TextureUsageInformation { public string MaterialPropertyName { get; } public UVChannel UVChannel { get; } From 2a423bfc977ecb137fab6404f6eec1fd98a75277 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 16 Sep 2024 17:22:06 +0900 Subject: [PATCH 27/30] chore: add minimal test for optimize texture --- .../TraceAndOptimize/OptimizeTexture.cs | 6 +- Test~/Basic/OptimizeTextureTest.cs | 56 +++++++++++++++++++ Test~/Basic/OptimizeTextureTest.cs.meta | 3 + 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 Test~/Basic/OptimizeTextureTest.cs create mode 100644 Test~/Basic/OptimizeTextureTest.cs.meta diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index 7ef02891c..eb64da49d 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -957,7 +957,7 @@ private static Texture2D CopyFromRenderTarget(RenderTexture source, Texture2D or return AfterAtlasSizesSmallToBigGenerator(totalIslandSize, new Vector2(maxIslandSizeX, maxIslandSizeY)); } - static IEnumerable AfterAtlasSizesSmallToBigGenerator(float useRatio, Vector2 maxIslandSize) + internal static IEnumerable AfterAtlasSizesSmallToBigGenerator(float useRatio, Vector2 maxIslandSize) { var maxHalfCount = 0; { @@ -980,7 +980,7 @@ static IEnumerable AfterAtlasSizesSmallToBigGenerator(float useRatio, V { var ySize = size / xSize; if (ySize < minYSize) break; - if (ySize > 1) break; + if (ySize > 1) continue; if (ySize >= 1 && xSize >= 1) break; yield return new Vector2(xSize, ySize); @@ -1036,7 +1036,7 @@ static bool TryAtlasTexture(AtlasIsland[] islands, Vector2 size) return true; } - class AtlasIsland + internal class AtlasIsland { //TODO: rotate public IslandUtility.Island OriginalIsland; diff --git a/Test~/Basic/OptimizeTextureTest.cs b/Test~/Basic/OptimizeTextureTest.cs new file mode 100644 index 000000000..4ab07719f --- /dev/null +++ b/Test~/Basic/OptimizeTextureTest.cs @@ -0,0 +1,56 @@ +using System.Linq; +using Anatawa12.AvatarOptimizer.Processors.TraceAndOptimizes; +using NUnit.Framework; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.Test; + +public class OptimizeTextureTest +{ + [Test] + public void TestAfterAtlasSizesSmallToBigGenerator() + { + // 0.0078125 = 1 / 128 + Assert.That( + OptimizeTextureImpl.AfterAtlasSizesSmallToBigGenerator(0.007813f, new Vector2(0.05f, 0.05f)).ToArray(), + Is.EqualTo(new[] + { + // size: 1 / 128 + new Vector2(0.0625f, 0.125f), + new Vector2(0.125f, 0.0625f), + + // size: 1 / 64 + new Vector2(0.0625f, 0.25f), + new Vector2(0.125f, 0.125f), + new Vector2(0.25f, 0.0625f), + + // size: 1 / 32 + new Vector2(0.0625f, 0.5f), + new Vector2(0.125f, 0.25f), + new Vector2(0.25f, 0.125f), + new Vector2(0.5f, 0.0625f), + + // size: 1 / 16 + new Vector2(0.0625f, 1.0f), + new Vector2(0.125f, 0.5f), + new Vector2(0.25f, 0.25f), + new Vector2(0.5f, 0.125f), + new Vector2(1.0f, 0.0625f), + + // size: 1 / 8 + new Vector2(0.125f, 1.0f), + new Vector2(0.25f, 0.5f), + new Vector2(0.5f, 0.25f), + new Vector2(1.0f, 0.125f), + + // size: 1 / 4 + new Vector2(0.25f, 1.0f), + new Vector2(0.5f, 0.5f), + new Vector2(1.0f, 0.25f), + + // size: 1 / 2 + new Vector2(0.5f, 1.0f), + new Vector2(1.0f, 0.5f), + })); + } +} \ No newline at end of file diff --git a/Test~/Basic/OptimizeTextureTest.cs.meta b/Test~/Basic/OptimizeTextureTest.cs.meta new file mode 100644 index 000000000..c57bd2b3f --- /dev/null +++ b/Test~/Basic/OptimizeTextureTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2cf17c7c732840cba0b3df66d0eace01 +timeCreated: 1726469597 \ No newline at end of file From 2f9fbc5b5e890d368148f8f0ab17bfe09e4f4de6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 16 Sep 2024 18:10:37 +0900 Subject: [PATCH 28/30] chore: create public api version of shader information --- API-Editor/ShaderInformation.cs | 359 +++++++++++++++++++++++++++ API-Editor/ShaderInformation.cs.meta | 3 + Test~/Basic/PublicApiCheck.cs | 10 +- 3 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 API-Editor/ShaderInformation.cs create mode 100644 API-Editor/ShaderInformation.cs.meta diff --git a/API-Editor/ShaderInformation.cs b/API-Editor/ShaderInformation.cs new file mode 100644 index 000000000..19db07a40 --- /dev/null +++ b/API-Editor/ShaderInformation.cs @@ -0,0 +1,359 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using UnityEditor; +using UnityEngine; +using ArgumentNullException = System.ArgumentNullException; + +namespace Anatawa12.AvatarOptimizer.API +{ + /// + /// The registry of shader information. + /// + /// You should register information on InitializeOnLoad or RuntimeInitializeOnLoadMethod or on creating the shader with constructor. + /// + [PublicAPI] + public static class ShaderInformationRegistry + { + private static Dictionary shaderInformation = new(); + private static Dictionary shaderInformationWithGUID = new(); + + /// + /// Register the shader information with the GUID of the shader. + /// + /// If the shader is registerd with both and this function, the information registered with will be used. + /// + /// The GUID of the shader to register the information. + /// The information to register. + /// + [PublicAPI] + public static IDisposable RegisterShaderInformationWithGUID(string guid, ShaderInformation information) + { + if (guid == null) throw new ArgumentNullException(nameof(guid)); + if (information == null) throw new ArgumentNullException(nameof(information)); + + if (shaderInformationWithGUID.TryAdd(guid, information)) + return new UnregisterGUIDDisposable(guid); + + if (shaderInformationWithGUID[guid].IsInternalInformation) + { + shaderInformationWithGUID[guid] = information; + return NoopDisposable.Instance; + } + + var existing = shaderInformationWithGUID[guid]; + if (existing.IsInternalInformation) + { + shaderInformationWithGUID[guid] = information; + return new UnregisterGUIDDisposable(guid); + } + + throw new InvalidOperationException("the shader is already registered."); + } + + /// + /// Register the shader information. + /// + /// If the shader is registered with both and , the information registered with will be used. + /// + /// + /// This function is mainly for runtime-generated shaders. + /// + /// If you have a shader as a asset, you should use instead since loading assets might not be possible to load on time. + /// + /// The shader to register the information. + /// The information to register. + /// The disposable to unregister the information. + /// or is null. + /// The shader is already registered. + [PublicAPI] + public static IDisposable RegisterShaderInformation(Shader shader, ShaderInformation information) + { + if (shader == null) throw new ArgumentNullException(nameof(shader)); + if (information == null) throw new ArgumentNullException(nameof(information)); + if (information.IsInternalInformation) throw new InvalidOperationException("the shader is already registered."); + + if (shaderInformation.TryAdd(shader, information)) + return new UnregisterDisposable(shader); + + var existing = shaderInformation[shader]; + if (existing.IsInternalInformation) + { + shaderInformation[shader] = information; + return new UnregisterDisposable(shader); + } + + throw new InvalidOperationException("the shader is already registered."); + } + + private class UnregisterDisposable : IDisposable + { + private readonly Shader _shader; + + public UnregisterDisposable(Shader shader) => _shader = shader; + + public void Dispose() => shaderInformation.Remove(_shader); + } + + private class UnregisterGUIDDisposable : IDisposable + { + private readonly string _guid; + public UnregisterGUIDDisposable(string guid) => _guid = guid; + public void Dispose() => shaderInformationWithGUID.Remove(_guid); + } + + private class NoopDisposable : IDisposable + { + public static NoopDisposable Instance { get; } = new(); + + public void Dispose() + { + } + } + } + + /// + /// The class that provides the information about the shader. + /// + /// If your shader is a Shader Suite, you can share shaderInformation class with multiple Shader asset. + /// + [PublicAPI] + public abstract class ShaderInformation + { + [PublicAPI] + protected ShaderInformation() + { + } + + /// + /// If this is true, some other information can override this information. + /// + internal virtual bool IsInternalInformation => false; + + /// + /// Gets the texture usage information for the material. + /// + /// This function should call the callback to provide the texture usage information for the material. + /// + /// The callback to provide the texture usage information for the material. + /// Whether the information is provided successfully. If false, the information is considered as not provided. + [PublicAPI] + public abstract bool GetTextureUsageInformationForMaterial(TextureUsageInformationCallback matInfo); + + // note for future this class update: this class is extandable public abstract class so you must not add new abstract method to this class. + } + + /// + /// The callback for the texture usage information. + /// + [PublicAPI] + public abstract class TextureUsageInformationCallback + { + internal TextureUsageInformationCallback() + { + } + + /// + /// Returns the integer value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + [PublicAPI] + public abstract int? GetInteger(string propertyName, bool considerAnimation = true); + + /// + /// Returns the float value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + [PublicAPI] + public abstract float? GetFloat(string propertyName, bool considerAnimation = true); + + /// + /// Returns the float value for the property in the material, or null if the property is not set or not found. + /// + /// The name of the property in the material. + /// Whether to consider the animation of the property. If this is true, this function will never + /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. + [PublicAPI] + public abstract Vector4? GetVector(string propertyName, bool considerAnimation = true); + + /// + /// Registers UV Usage that are not considered by Avatar Optimizer. + /// + /// This will the UV Channel not affected by optimizations of Avatar Optimizer. + /// + /// The UVChannels that are used in the shader. + [PublicAPI] + public abstract void RegisterOtherUVUsage(UsingUVChannels uvChannel); + + /// + /// Registers Texture Usage and UV Usage that are considered by Avatar Optimizer. + /// + /// The texture might go to the atlas / UV Packing if the UsingUVChannels is set and the UV Matrix is known + /// + /// The name of the texture property in the material. + /// The information about the sampler state used for the specified texture. + /// The UVChannels that are used in the shader to determine the UV for the texture. + /// The UV Transform Matrix for the texture. This includes textureName_ST scale offset. Null if the UV transfrom is not known. + /// + /// This section describes the current and planned implementation of UV Packing in the Avatar Optimizer about this function. + /// + /// Currently, Avatar Optimizer does UV Packing if (non-exclusive): + /// - Texture is reasonably used by small set of materials + /// - UsingUVChannels is set to only one of UV Channels (per material) + /// - UV Matrix is known and identity matrix + /// + /// However, Avatar Optimizer will support more complex UV Packing in the future: + /// - Support UV Matrix with scale is smaller and rotation is multiple of 90 degrees + /// - multiple UV Channel texture + /// + [PublicAPI] + public abstract void RegisterTextureUVUsage( + string textureMaterialPropertyName, + SamplerStateInformation samplerState, + UsingUVChannels uvChannels, + Matrix4x4? uvMatrix); + } + + /// + /// The flags to express which UV Channels might be used in the shader. + /// + /// Usage of the UV channels might be specified with some other APIs. + /// + [Flags] + [PublicAPI] + public enum UsingUVChannels + { + UV0 = 1, + UV1 = 2, + UV2 = 4, + UV3 = 8, + UV4 = 16, + UV5 = 32, + UV6 = 64, + UV7 = 128, + + /// + /// The UV Channels not from the Mesh UV. + /// For example, screenspace or color. + /// + NonMesh = 256, + Unknown = 0x7FFFFFFF, + } + + /// + /// The information about the sampler state for the specified texture. + /// + /// You can combine multiple SamplerStateInformation for the texture with `|` operator. + /// + /// You can cast string to SamplerStateInformation to use the sampler state for + /// the specified texture like sampler_MainTex by (SamplerStateInformation)"_MainTex". + /// + /// If your shader is using hardcoded sampler state, you can use the predefined sampler state like + /// or . + /// + [PublicAPI] + public readonly struct SamplerStateInformation + { + private readonly string _textureName; + private readonly bool _materialProperty; + + [PublicAPI] + public SamplerStateInformation(string textureName) + { + _textureName = textureName; + _materialProperty = true; + } + + // construct builtin non-material property sampler state + private SamplerStateInformation(string textureName, bool dummy) + { + _textureName = textureName; + _materialProperty = false; + } + + // I don't want to expose equals to public API so I made this internal function instead of overriding Equals + internal static bool EQ(SamplerStateInformation left, SamplerStateInformation right) + { + if (left._materialProperty != right._materialProperty) return false; + if (left._textureName != right._textureName) return false; + return true; + } + + /// Unknown Sampler. The Avatar Optimizer will never optimize depends on sampler state information + [PublicAPI] + public static SamplerStateInformation Unknown { get; } = new("Unknown", false); + + /// The hard-coded inline Sampler with clamp texture wrapping and point texture filtering mode + [PublicAPI] + public static SamplerStateInformation PointClampSampler { get; } = new("PointClamp", false); + + /// The hard-coded inline Sampler with repeat texture wrapping and point texture filtering mode + [PublicAPI] + public static SamplerStateInformation PointRepeatSampler { get; } = new("PointRepeat", false); + + /// The hard-coded inline Sampler with mirror texture wrapping and point texture filtering mode + [PublicAPI] + public static SamplerStateInformation PointMirrorSampler { get; } = new("PointMirror", false); + + /// The hard-coded inline Sampler with mirror-once texture wrapping and point texture filtering mode + [PublicAPI] + public static SamplerStateInformation PointMirrorOnceSampler { get; } = + new("PointMirrorOnce", false); + + /// The hard-coded inline Sampler with clamp texture wrapping and linear texture filtering mode + [PublicAPI] + public static SamplerStateInformation LinearClampSampler { get; } = new("LinearClamp", false); + + /// The hard-coded inline Sampler with repeat texture wrapping and linear texture filtering mode + [PublicAPI] + public static SamplerStateInformation LinearRepeatSampler { get; } = new("LinearRepeat", false); + + /// The hard-coded inline Sampler with mirror texture wrapping and linear texture filtering mode + [PublicAPI] + public static SamplerStateInformation LinearMirrorSampler { get; } = new("LinearMirror", false); + + /// The hard-coded inline Sampler with mirror-once texture wrapping and linear texture filtering mode + [PublicAPI] + public static SamplerStateInformation LinearMirrorOnceSampler { get; } = + new("LinearMirrorOnce", false); + + /// The hard-coded inline Sampler with clamp texture wrapping and anisotropic texture filtering mode + [PublicAPI] + public static SamplerStateInformation TrilinearClampSampler { get; } = new("TrilinearClamp", false); + + /// The hard-coded inline Sampler with repeat texture wrapping and anisotropic texture filtering mode + [PublicAPI] + public static SamplerStateInformation TrilinearRepeatSampler { get; } = + new("TrilinearRepeat", false); + + /// The hard-coded inline Sampler with mirror texture wrapping and anisotropic texture filtering mode + [PublicAPI] + public static SamplerStateInformation TrilinearMirrorSampler { get; } = + new("TrilinearMirror", false); + + /// The hard-coded inline Sampler with mirror-once texture wrapping and anisotropic texture filtering mode + [PublicAPI] + public static SamplerStateInformation TrilinearMirrorOnceSampler { get; } = + new("TrilinearMirrorOnce", false); + + [PublicAPI] + public static implicit operator SamplerStateInformation(string textureName) => new(textureName); + + [PublicAPI] + public static SamplerStateInformation operator |(SamplerStateInformation left, SamplerStateInformation right) => + Combine(left, right); + + private static SamplerStateInformation Combine(SamplerStateInformation left, SamplerStateInformation right) + { + // we may implement better logic in the future + if (EQ(left, right)) return left; + return Unknown; + } + } +} diff --git a/API-Editor/ShaderInformation.cs.meta b/API-Editor/ShaderInformation.cs.meta new file mode 100644 index 000000000..a7ecc1d80 --- /dev/null +++ b/API-Editor/ShaderInformation.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 95bcaf53684449f5b2508181ed6b7bc2 +timeCreated: 1726475104 \ No newline at end of file diff --git a/Test~/Basic/PublicApiCheck.cs b/Test~/Basic/PublicApiCheck.cs index b9306ef58..8add1214a 100644 --- a/Test~/Basic/PublicApiCheck.cs +++ b/Test~/Basic/PublicApiCheck.cs @@ -135,10 +135,14 @@ public void CheckPublicApi(Type type) } // fields are not allowed to be public - foreach (var fieldInfo in type.GetFields(AllMembers)) + // for enum, we do not check PublicAPIAttribute + if (!type.IsEnum) { - Assert.That(IsPubliclyAccessible(fieldInfo.Attributes, allowInherit), Is.False, - $"{fieldInfo} is publicly accessible but not marked as PublicAPIAttribute"); + foreach (var fieldInfo in type.GetFields(AllMembers)) + { + Assert.That(IsPubliclyAccessible(fieldInfo.Attributes, allowInherit), Is.False, + $"{fieldInfo} is fields are publicly accessible"); + } } } From afc736a2ead03a80dee58b8c26128f0c144e72c5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 16 Sep 2024 19:49:02 +0900 Subject: [PATCH 29/30] refactor(shader-knowladge): rewrite with public api version of shader information --- API-Editor/ShaderInformation.cs | 8 + .../APIInternal/ShaderInformation.Liltoon.cs | 789 ++++++++++++++++++ .../ShaderInformation.Liltoon.cs.meta | 3 + .../TraceAndOptimize/OptimizeTexture.cs | 37 +- Editor/ShaderKnowledge.cs | 769 ----------------- 5 files changed, 820 insertions(+), 786 deletions(-) create mode 100644 Editor/APIInternal/ShaderInformation.Liltoon.cs create mode 100644 Editor/APIInternal/ShaderInformation.Liltoon.cs.meta diff --git a/API-Editor/ShaderInformation.cs b/API-Editor/ShaderInformation.cs index 19db07a40..5f60fd710 100644 --- a/API-Editor/ShaderInformation.cs +++ b/API-Editor/ShaderInformation.cs @@ -112,6 +112,14 @@ public void Dispose() { } } + + internal static ShaderInformation? GetShaderInformation(Shader shader) + { + if (shaderInformation.TryGetValue(shader, out var info)) return info; + var guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(shader)); + if (shaderInformationWithGUID.TryGetValue(guid, out var infoWithGUID)) return infoWithGUID; + return null; + } } /// diff --git a/Editor/APIInternal/ShaderInformation.Liltoon.cs b/Editor/APIInternal/ShaderInformation.Liltoon.cs new file mode 100644 index 000000000..66bf0404f --- /dev/null +++ b/Editor/APIInternal/ShaderInformation.Liltoon.cs @@ -0,0 +1,789 @@ +using System; +using System.Reflection; +using Anatawa12.AvatarOptimizer.API; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.APIInternal; + +[InitializeOnLoad] +internal class LiltoonShaderInformation : ShaderInformation +{ + internal override bool IsInternalInformation => true; + + static LiltoonShaderInformation() + { + Register(); + } + + private static void Register() + { + // get current version value + if (Utils.GetTypeFromName("lilToon.lilConstants") is not {} lilConstantsType) return; + if (lilConstantsType.GetField("currentVersionValue", BindingFlags.Public | BindingFlags.Static) is not {} currentVersionValueField) return; + if (currentVersionValueField.GetValue(null) is not int versionValue) return; + + // check if version is supported version + if (versionValue > supportedLiltoon) return; + + var information = new LiltoonShaderInformation(); + foreach (var guid in guids) + { + ShaderInformationRegistry.RegisterShaderInformationWithGUID(guid, information); + } + } + + private static int supportedLiltoon = 43; + + private static string[] guids = + { + "544c75f56e9af8048b29a6ace5f52091", // lts_fur_cutout.shader + "7ec9f85eb7ee04943adfe19c2ba5901f", // lts_furonly_cutout.shader + "00795bf598b44dc4e9bd363348e77085", // lts_fakeshadow.shader + "3eef4aee6ba0de047b0d40409ea2891c", // lts_tess.shader + "61b4f98a5d78b4a4a9d89180fac793fc", // ltspass_opaque.shader + "df12117ecd77c31469c224178886498e", // lts.shader + "dce3f3e248acc7b4daeda00daf616b4d", // lts_ref.shader + "14006db8206fb304aa86110d57626d40", // ltspass_tess_cutout.shader + "bdf24b2e925ce8a4fb0e903889a52e0e", // ltspass_tess_transparent.shader + "fba17785d6b2c594ab6c0303c834da65", // lts_oo.shader + "62df797f407281640a224388953448cc", // ltsl_twotrans_o.shader + "afa1a194f5a2fd243bda3a17bca1b36e", // lts_tess_trans.shader + "b269573b9937b8340b3e9e191a3ba5a8", // lts_onetrans.shader + "165365ab7100a044ca85fc8c33548a62", // lts_trans.shader + "9294844b15dca184d914a632279b24e1", // ltsmulti.shader + "59b5e58e88aae8a4ca42d1a7253e2fb2", // ltspass_lite_opaque.shader + "583a88005abb81a4ebbce757b4851a0d", // ltsl_o.shader + "82226adb1a0b8c4418f574cfdcf523da", // ltsl_twotrans.shader + "7171688840c632447b22ec14e2bdef7e", // lts_onetrans_o.shader + "c6d605ee23b18fc46903f38c67db701f", // lts_tess_o.shader + "33aad051c4a3a844a8f9330addb86a97", // lts_furonly.shader + "0e3ece1bd59542743bccadb21f68318e", // ltsl_trans.shader + "381af8ba8e1740a41b9768ccfb0416c2", // ltsl.shader + "d7af54cdd86902d41b8c240e06b93009", // ltsmulti_ref.shader + "8cf5267d397b04846856f6d3d9561da0", // ltsl_cutout_o.shader + "90f83c35b0769a748abba5d0880f36d5", // lts_tess_onetrans.shader + "3c79b10c7e0b2784aaa4c2f8dd17d55e", // lts_trans_o.shader + "85d6126cae43b6847aff4b13f4adb8ec", // lts_cutout.shader + "3b3957e6c393b114bab6f835b4ed8f5d", // lts_cutout_oo.shader + "d28e4b78ba8368e49a44f86c0291df58", // ltsl_overlay.shader + "2683fad669f20ec49b8e9656954a33a8", // ltspass_transparent.shader + "7e61dbad981ad4f43a03722155db1c6a", // lts_tess_twotrans_o.shader + "67ed0252d63362a4ab23707a720508b7", // lts_tess_onetrans_o.shader + "7e398ea50f9b70045b1774e05b46a39f", // lts_tess_twotrans.shader + "7a7ac427f85673a45a3e4190fc10bc28", // ltspass_tess_opaque.shader + "9b0c2630b12933248922527d4507cfa9", // lts_tess_trans_o.shader + "33e950d038b8dfd4f824f3985c2abfb7", // lts_overlay_one.shader + "efa77a80ca0344749b4f19fdd5891cbe", // lts_o.shader + "8a6ef0489c3ffbf46812460af3d52bb0", // ltspass_lite_cutout.shader + "f8d9dfac6dbfaaf4c9c3aaf4bd8c955f", // lts_furonly_two.shader + "3fb94a39b2685ee4d9817dcaf6542d99", // lts_ref_blur.shader + "69f861c14129e724096c0955f8079012", // ltsmulti_gem.shader + "3b4aa19949601f046a20ca8bdaee929f", // lts_cutout_o.shader + "0c762f24b85918a49812fc5690619178", // lts_trans_oo.shader + "ad219df2a46e841488aee6a013e84e36", // ltspass_cutout.shader + "55706696b2bdb5d4d8541b89e17085c8", // lts_fur.shader + "94274b8ef5d3af842b9427384cba3a8f", // lts_overlay.shader + "fd68f52288a6b0243bf6c217bf0930ea", // ltspass_proponly.shader + "9cf054060007d784394b8b0bb703e441", // lts_twotrans_o.shader + "6a77405f7dfdc1447af58854c7f43f39", // lts_twotrans.shader + "bbfffd5515b843c41a85067191cbf687", // lts_tess_cutout.shader + "dc9ded9f9d6f16c4e92cbb8f4269ae31", // ltsl_overlay_one.shader + "2bde4bd29a2a70a4d9cf98772a6717ac", // ltspass_dummy.shader + "34c2907eba944ed45a43970e0c11bcfd", // ltsl_onetrans.shader + "a8d94439709469942bc7dcc9156ba110", // lts_gem.shader + "701268c07d37f5441b25b2cb99fae4b3", // ltsl_onetrans_o.shader + "51b2dee0ab07bd84d8147601ff89e511", // ltsmulti_o.shader + "1c12a37046f07ac4486881deaf0187ea", // ltsl_trans_o.shader + "b957dce3d03ff5445ac989f8de643c7f", // ltsl_cutout.shader + "5ba517885727277409feada18effa4a6", // lts_tess_cutout_o.shader + "8773c83ab40fff24b800f74360819a6c", // ltspass_lite_transparent.shader + "54bc8b41278802d4a81b27fe402994e2", // lts_fur_two.shader + "1e50f1bc4d1b0e34cbf16b82589f6407", // ltsmulti_fur.shader + "f96a89829ccb1e54b85214550519a8d6", // ltspass_baker.shader + }; + + public override bool GetTextureUsageInformationForMaterial(TextureUsageInformationCallback matInfo) + { + // TODO: version check + + var uvMain = UsingUVChannels.UV0; + var uvMainScaleOffset = "_MainTex_ST"; + UnityEngine.Matrix4x4? uvMainMatrix = ComputeUVMainMatrix(); + + UnityEngine.Matrix4x4? ComputeUVMainMatrix() + { + // _ShiftBackfaceUV + if (matInfo.GetFloat("_ShiftBackfaceUV") != 0) return null; // changed depends on face + return STAndScrollRotateToMatrix("_MainTex_ST", "_MainTex_ScrollRotate"); + } + + matInfo.RegisterTextureUVUsage("_DitherTex", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.NonMesh, null); // dither UV is based on screen space + + // TODO: _MainTex with POM / PARALLAX (using LIL_SAMPLE_2D_POM) + LIL_SAMPLE_2D_WithMat("_MainTex", "_MainTex", uvMain, uvMainMatrix); // main texture + matInfo.RegisterTextureUVUsage("_MainGradationTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh, null); // GradationMap UV is based on color + LIL_SAMPLE_2D_WithMat("_MainColorAdjustMask", "_MainTex", uvMain, uvMainMatrix); // simple LIL_SAMPLE_2D + + if (matInfo.GetInteger("_UseMain2ndTex") != 0) + { + // caller of lilGetMain2nd will pass sampler for _MainTex as samp + SamplerStateInformation samp = "_MainTex"; + + UsingUVChannels uv2nd; + switch (matInfo.GetInteger("_Main2ndTex_UVMode")) + { + case 0: + uv2nd = UsingUVChannels.UV0; + break; + case 1: + uv2nd = UsingUVChannels.UV1; + break; + case 2: + uv2nd = UsingUVChannels.UV2; + break; + case 3: + uv2nd = UsingUVChannels.UV3; + break; + case 4: + uv2nd = UsingUVChannels.NonMesh; + break; // MatCap (normal-based UV) + default: + uv2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; + break; + } + + LIL_GET_SUBTEX("_Main2ndTex", uv2nd); + LIL_SAMPLE_2D_WithMat("_Main2ndBlendMask", samp, uvMain, uvMainMatrix); + lilCalcDissolveWithOrWithoutNoise( + UsingUVChannels.UV0, + "_Main2ndDissolveMask", + "_Main2ndDissolveMask_ST", + "_Main2ndDissolveNoiseMask", + "_Main2ndDissolveNoiseMask_ST", + "_Main2ndDissolveNoiseMask_ScrollRotate", + samp + ); + } + + if (matInfo.GetInteger("_UseMain3rdTex") != 0) + { + // caller of lilGetMain3rd will pass sampler for _MainTex as samp + var samp = "_MainTex"; + + UsingUVChannels uv3rd; + switch (matInfo.GetInteger("_Main2ndTex_UVMode")) + { + case 0: + uv3rd = UsingUVChannels.UV0; + break; + case 1: + uv3rd = UsingUVChannels.UV1; + break; + case 2: + uv3rd = UsingUVChannels.UV2; + break; + case 3: + uv3rd = UsingUVChannels.UV3; + break; + case 4: + uv3rd = UsingUVChannels.NonMesh; + break; // MatCap (normal-based UV) + default: + uv3rd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; + break; + } + + LIL_GET_SUBTEX("_Main3rdTex", uv3rd); + LIL_SAMPLE_2D_WithMat("_Main3rdBlendMask", samp, uvMain, uvMainMatrix); + lilCalcDissolveWithOrWithoutNoise( + UsingUVChannels.UV0, + "_Main3rdDissolveMask", + "_Main3rdDissolveMask_ST", + "_Main3rdDissolveNoiseMask", + "_Main3rdDissolveNoiseMask_ST", + "_Main3rdDissolveNoiseMask_ScrollRotate", + samp + ); + } + + LIL_SAMPLE_2D_ST_WithMat("_AlphaMask", "_MainTex", uvMain, uvMainMatrix); + if (matInfo.GetInteger("_UseBumpMap") != 0) + { + LIL_SAMPLE_2D_ST_WithMat("_BumpMap", "_MainTex", uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_UseBump2ndMap") != 0) + { + var uvBump2nd = UsingUVChannels.UV0; + + switch (matInfo.GetInteger("_Bump2ndMap_UVMode")) + { + case 0: + uvBump2nd = UsingUVChannels.UV0; + break; + case 1: + uvBump2nd = UsingUVChannels.UV1; + break; + case 2: + uvBump2nd = UsingUVChannels.UV2; + break; + case 3: + uvBump2nd = UsingUVChannels.UV3; + break; + case null: + uvBump2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; + break; + } + + LIL_SAMPLE_2D_ST("_Bump2ndMap", SamplerStateInformation.LinearRepeatSampler, uvBump2nd); + LIL_SAMPLE_2D_ST_WithMat("_Bump2ndScaleMask", "_MainTex", uvMain, uvMainMatrix); + + // Note: _Bump2ndScaleMask is defined as NoScaleOffset but sampled with LIL_SAMPLE_2D_ST? + } + + if (matInfo.GetInteger("_UseAnisotropy") != 0) + { + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyTangentMap", "_MainTex", uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyScaleMask", "_MainTex", uvMain, uvMainMatrix); + + // _AnisotropyShiftNoiseMask is used in another place but under _UseAnisotropy condition + LIL_SAMPLE_2D_ST_WithMat("_AnisotropyShiftNoiseMask", "_MainTex", uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_UseBacklight") != 0) + { + var samp = "_MainTex"; + LIL_SAMPLE_2D_ST_WithMat("_BacklightColorTex", samp, uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_UseShadow") != 0) + { + SamplerStateInformation samp = "_MainTex"; + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowStrengthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, + uvMainMatrix); + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBorderMask", SamplerStateInformation.LinearRepeatSampler, uvMain, + uvMainMatrix); + LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBlurMask", SamplerStateInformation.LinearRepeatSampler, uvMain, + uvMainMatrix); + // lilSampleLUT + switch (matInfo.GetInteger("_ShadowColorType")) + { + case 1: + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh, null); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh, null); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh, null); + break; + case null: + var sampler = samp | SamplerStateInformation.LinearClampSampler; + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); + break; + default: + LIL_SAMPLE_2D_WithMat("_ShadowColorTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", samp, uvMain, uvMainMatrix); + break; + } + } + + if (matInfo.GetInteger("_UseRimShade") != 0) + { + var samp = "_MainTex"; + + LIL_SAMPLE_2D_WithMat("_RimShadeMask", samp, uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_UseReflection") != 0) + { + // TODO: research + var samp = "_MainTex"; // or SamplerStateInformation.LinearRepeatSampler in lil_pass_foreward_reblur.hlsl + + LIL_SAMPLE_2D_ST_WithMat("_SmoothnessTex", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_MetallicGlossMap", samp, uvMain, uvMainMatrix); + LIL_SAMPLE_2D_ST_WithMat("_ReflectionColorTex", samp, uvMain, uvMainMatrix); + } + + // Matcap + if (matInfo.GetInteger("_UseMatCap") != 0) + { + var samp = "_MainTex"; // caller of lilGetMatCap + + LIL_SAMPLE_2D("_MatCapTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); + LIL_SAMPLE_2D_ST_WithMat("_MatCapBlendMask", samp, uvMain, uvMainMatrix); + + if (matInfo.GetInteger("_MatCapCustomNormal") != 0) + { + LIL_SAMPLE_2D_ST_WithMat("_MatCapBumpMap", samp, uvMain, uvMainMatrix); + } + } + + if (matInfo.GetInteger("_UseMatCap2nd") != 0) + { + var samp = "_MainTex"; // caller of lilGetMatCap + + LIL_SAMPLE_2D("_MatCap2ndTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); + LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBlendMask", samp, uvMain, uvMainMatrix); + + if (matInfo.GetInteger("_MatCap2ndCustomNormal") != 0) + { + LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBumpMap", samp, uvMain, uvMainMatrix); + } + } + + // rim light + if (matInfo.GetInteger("_UseRim") != 0) + { + var samp = "_MainTex"; // caller of lilGetRim + LIL_SAMPLE_2D_ST_WithMat("_RimColorTex", samp, uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_UseGlitter") != 0) + { + var samp = "_MainTex"; // caller of lilGetGlitter + + LIL_SAMPLE_2D_ST_WithMat("_GlitterColorTex", samp, uvMain, uvMainMatrix); + if (matInfo.GetInteger("_GlitterApplyShape") != 0) + { + // complex uv + LIL_SAMPLE_2D_GRAD("_GlitterShapeTex", SamplerStateInformation.LinearClampSampler, + UsingUVChannels.NonMesh); + } + } + + if (matInfo.GetInteger("_UseEmission") != 0) + { + UsingUVChannels emissionUV = UsingUVChannels.UV0; + + switch (matInfo.GetInteger("_EmissionMap_UVMode")) + { + case 1: + emissionUV = UsingUVChannels.UV1; + break; + case 2: + emissionUV = UsingUVChannels.UV2; + break; + case 3: + emissionUV = UsingUVChannels.UV3; + break; + case 4: + emissionUV = UsingUVChannels.NonMesh; + break; // uvRim; TODO: check + case null: + emissionUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3 | + UsingUVChannels.NonMesh; + break; + } + + var parallaxEnabled = matInfo.GetFloat("_EmissionParallaxDepth") != 0; + + LIL_GET_EMITEX("_EmissionMap", emissionUV, parallaxEnabled); + + // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. + var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = + matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || + matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); + + if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) + { + LIL_GET_EMIMASK("_EmissionBlendMask", UsingUVChannels.UV0); + } + else + { + LIL_GET_EMIMASK_WithMat("_EmissionBlendMask", uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_EmissionUseGrad") != 0) + { + LIL_SAMPLE_1D("_EmissionGradTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); + } + } + + if (matInfo.GetInteger("_Emission2ndMap") != 0) + { + UsingUVChannels emission2ndUV = UsingUVChannels.UV0; + + switch (matInfo.GetInteger("_Emission2ndMap_UVMode")) + { + case 1: + emission2ndUV = UsingUVChannels.UV1; + break; + case 2: + emission2ndUV = UsingUVChannels.UV2; + break; + case 3: + emission2ndUV = UsingUVChannels.UV3; + break; + case 4: + emission2ndUV = UsingUVChannels.NonMesh; + break; // uvRim; TODO: check + case null: + emission2ndUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | + UsingUVChannels.UV3 | UsingUVChannels.NonMesh; + break; + } + + var parallaxEnabled = matInfo.GetFloat("_Emission2ndParallaxDepth") != 0; + + // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST + LIL_GET_EMITEX("_Emission2ndMap", emission2ndUV, parallaxEnabled); + + // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. (weird) + // https://github.com/lilxyzw/lilToon/blob/b96470d3dd9092b840052578048b2307fe6d8786/Assets/lilToon/Shader/Includes/lil_common_frag.hlsl#L1819-L1821 + var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = + matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || + matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); + + if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) + { + LIL_GET_EMIMASK("_Emission2ndBlendMask", UsingUVChannels.UV0); + } + else + { + LIL_GET_EMIMASK_WithMat("_Emission2ndBlendMask", uvMain, uvMainMatrix); + } + + if (matInfo.GetInteger("_Emission2ndUseGrad") != 0) + { + LIL_SAMPLE_1D("_Emission2ndGradTex", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.NonMesh); + } + } + + if (matInfo.GetInteger("_UseParallax") != 0) + { + matInfo.RegisterTextureUVUsage("_ParallaxMap", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV0, null); + } + + if (matInfo.GetInteger("_UseAudioLink") != 0 && matInfo.GetInteger("_AudioLink2Vertex") != 0) + { + var _AudioLinkUVMode = matInfo.GetInteger("_AudioLinkUVMode"); + + if (_AudioLinkUVMode is 3 or 4 or null) + { + // TODO: _AudioLinkMask_ScrollRotate + var sampler = "_AudioLinkMask" | SamplerStateInformation.LinearRepeatSampler; + switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) + { + case 0: + default: + LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, uvMain, uvMainMatrix); + break; + case 1: + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV1); + break; + case 2: + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV2); + break; + case 3: + LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV3); + break; + case null: + LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, + uvMain | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, + Combine(uvMainMatrix, Matrix4x4.identity)); + break; + } + } + } + + if (matInfo.GetVector("_DissolveParams")?.x != 0) + { + lilCalcDissolveWithOrWithoutNoise( + //fd.col.a, + //dissolveAlpha, + UsingUVChannels.UV0, + //fd.positionOS, + //_DissolveParams, + //_DissolvePos, + "_DissolveMask", + "_DissolveMask_ST", + //_DissolveMaskEnabled, + "_DissolveNoiseMask", + "_DissolveNoiseMask_ST", + "_DissolveNoiseMask_ScrollRotate", + //_DissolveNoiseStrength + "_MainTex" + ); + } + + if (matInfo.GetInteger("_UseOutline") != 0) + { + // not on material side, on editor side toggle + LIL_SAMPLE_2D_WithMat("_OutlineTex", "_OutlineTex", uvMain, uvMainMatrix); + LIL_SAMPLE_2D_WithMat("_OutlineWidthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, + uvMainMatrix); + // _OutlineVectorTex SamplerStateInformation.LinearRepeatSampler + // UVs _OutlineVectorUVMode main,1,2,3 + + switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) + { + case 0: + LIL_SAMPLE_2D_WithMat("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, uvMain, + uvMainMatrix); + break; + case 1: + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV1); + break; + case 2: + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV2); + break; + case 3: + LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV3); + break; + default: + case null: + matInfo.RegisterTextureUVUsage( + "_OutlineVectorTex", + SamplerStateInformation.LinearRepeatSampler, + UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, + Combine(uvMainMatrix, UnityEngine.Matrix4x4.identity) + ); + break; + } + } + + // _BaseMap and _BaseColorMap are unused + + return true; + + void LIL_SAMPLE_1D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + UnityEngine.Matrix4x4.identity + ); + } + + void LIL_SAMPLE_2D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + // might be _LOD: using SampleLevel + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + UnityEngine.Matrix4x4.identity + ); + } + + void LIL_SAMPLE_2D_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, + UnityEngine.Matrix4x4? matrix) + { + // might be _LOD: using SampleLevel + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + matrix + ); + } + + void LIL_SAMPLE_2D_GRAD(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + // additional parameter for SampleGrad does not affect UV location much + LIL_SAMPLE_2D(textureName, samplerName, uvChannel); + } + + void LIL_SAMPLE_2D_GRAD_WithMat(string textureName, SamplerStateInformation samplerName, + UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + // additional parameter for SampleGrad does not affect UV location much + LIL_SAMPLE_2D_WithMat(textureName, samplerName, uvChannel, matrix); + } + + void LIL_SAMPLE_2D_ST(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + STToMatrix($"{textureName}_ST") + ); + } + + void LIL_SAMPLE_2D_ST_WithMat(string textureName, SamplerStateInformation samplerName, + UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + matInfo.RegisterTextureUVUsage( + textureName, + samplerName, + uvChannel, + Multiply(STToMatrix($"{textureName}_ST"), matrix) + ); + } + + void LIL_GET_SUBTEX(string textureName, UsingUVChannels uvChannel) + { + // lilGetSubTex + + // TODO: consider the following properties + var st = $"{textureName}_ST"; + var scrollRotate = $"{textureName}_ScrollRotate"; + var angle = $"{textureName}Angle"; + var isDecal = $"{textureName}IsDecal"; + var isLeftOnly = $"{textureName}IsLeftOnly"; + var isRightOnly = $"{textureName}IsRightOnly"; + var shouldCopy = $"{textureName}ShouldCopy"; + var shouldFlipMirror = $"{textureName}ShouldFlipMirror"; + var shouldFlipCopy = $"{textureName}ShouldFlipCopy"; + var isMSDF = $"{textureName}IsMSDF"; + var decalAnimation = $"{textureName}DecalAnimation"; + var decalSubParam = $"{textureName}DecalSubParam"; + // fd.nv? + // fd.isRightHand? + + + Matrix4x4? ComputeMatrix() + { + var stValueOpt = matInfo.GetVector(st); + var rotateValueOpt = matInfo.GetVector(scrollRotate); + var angleValueOpt = matInfo.GetFloat(angle); + //var isDecalValueOpt = matInfo.GetFloat(isDecal); + var isLeftOnlyValueOpt = matInfo.GetFloat(isLeftOnly); + var isRightOnlyValueOpt = matInfo.GetFloat(isRightOnly); + var shouldCopyValueOpt = matInfo.GetFloat(shouldCopy); + var shouldFlipMirrorValueOpt = matInfo.GetFloat(shouldFlipMirror); + var shouldFlipCopyValueOpt = matInfo.GetFloat(shouldFlipCopy); + //var isMSDFValueOpt = matInfo.GetFloat(isMSDF); + var decalAnimationValueOpt = matInfo.GetVector(decalAnimation); + // var decalSubParamValueOpt = matInfo.GetVector(decalSubParam); + + if (stValueOpt is not { } stValue) return null; + if (rotateValueOpt is not { } rotateValue) return null; + if (angleValueOpt is not { } angleValue) return null; + + rotateValue.z = angleValue; + + if (STAndScrollRotateValueToMatrix(stValue, rotateValue) is not { } matrix) return null; + + // shouldCopy is true => x = abs(x - 0.5) + 0.5 + if (shouldCopyValueOpt != 0) return null; + // shouldFlipCopy is true => flips + if (shouldFlipCopyValueOpt != 0) return null; + // shouldFlipMirror is true => flips + if (shouldFlipMirrorValueOpt != 0) return null; + + // isDecal is true => decal + if (isLeftOnlyValueOpt != 0) return null; + if (isRightOnlyValueOpt != 0) return null; + + // rotation is performed in STAndScrollRotateValueToMatrix + + if (decalAnimationValueOpt != new Vector4(1.0f, 1.0f, 1.0f, 30.0f)) return null; + + return matrix; + } + + matInfo.RegisterTextureUVUsage(textureName, textureName, uvChannel, ComputeMatrix()); + } + + void LIL_GET_EMITEX(string textureName, UsingUVChannels uvChannel, bool parallaxEnabled) + { + LIL_SAMPLE_2D_WithMat(textureName, textureName, uvChannel, + parallaxEnabled ? null : STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); + } + + void LIL_GET_EMIMASK_WithMat(string textureName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) + { + LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, + Multiply(matrix, STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate"))); + } + + void LIL_GET_EMIMASK(string textureName, UsingUVChannels uvChannel) + { + LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, + STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); + } + + void lilCalcDissolveWithOrWithoutNoise( + // alpha, + // dissolveAlpha, + UsingUVChannels uv, // ? + // positionOS, + // dissolveParams, + // dissolvePos, + string dissolveMask, + string dissolveMaskST, + // dissolveMaskEnabled + string dissolveNoiseMask, + string dissolveNoiseMaskST, + string dissolveNoiseMaskScrollRotate, + // dissolveNoiseStrength, + SamplerStateInformation samp + ) + { + LIL_SAMPLE_2D_WithMat(dissolveMask, samp, uv, STToMatrix(dissolveMaskST)); + LIL_SAMPLE_2D_WithMat(dissolveNoiseMask, samp, uv, + STAndScrollRotateToMatrix(dissolveNoiseMaskST, dissolveNoiseMaskScrollRotate)); + } + + // lilCalcUV + Matrix4x4? STToMatrix(string stPropertyName) => STValueToMatrix(matInfo.GetVector(stPropertyName)); + + Matrix4x4? STValueToMatrix(Vector4? stIn) + { + if (stIn is not { } st) return null; + + var matrix = Matrix4x4.identity; + matrix.m00 = st.x; + matrix.m11 = st.y; + matrix.m03 = st.z; + matrix.m13 = st.w; + + return matrix; + } + + // lilCalcUV + Matrix4x4? STAndScrollRotateToMatrix(string stPropertyName, string scrollRotatePropertyName) => + STAndScrollRotateValueToMatrix(matInfo.GetVector(stPropertyName), + matInfo.GetVector(scrollRotatePropertyName)); + + Matrix4x4? STAndScrollRotateValueToMatrix(Vector4? stValueIn, Vector4? scrollRotateIn) + { + if (STValueToMatrix(stValueIn) is not { } stMatrix) return null; + if (scrollRotateIn is not { } scrollRotate) return stMatrix; + + float staticAngle = scrollRotate.z; + float scrollAngleSpeed = scrollRotate.w; + Vector2 scrollSpeed = new(scrollRotate.x, scrollRotate.y); + + if (scrollSpeed != Vector2.zero || scrollAngleSpeed != 0) return null; + + if (staticAngle == 0) return stMatrix; + + var result = stMatrix; + + result *= Matrix4x4.TRS(new Vector3(-0.5f, -0.5f), Quaternion.identity, Vector3.one); + result *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, staticAngle), Vector3.one); + result *= Matrix4x4.TRS(new Vector3(0.5f, 0.5f), Quaternion.identity, Vector3.one); + + return result; + } + + static Matrix4x4? Combine(Matrix4x4? a, Matrix4x4? b) + { + if (a == null) return b; + if (b == null) return a; + if (a == b) return a; + return null; + } + + Matrix4x4? Multiply(Matrix4x4? a, Matrix4x4? b) + { + if (a == null || b == null) return null; + return a.Value * b.Value; + } + } +} diff --git a/Editor/APIInternal/ShaderInformation.Liltoon.cs.meta b/Editor/APIInternal/ShaderInformation.Liltoon.cs.meta new file mode 100644 index 000000000..dbdbfd003 --- /dev/null +++ b/Editor/APIInternal/ShaderInformation.Liltoon.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a33c006ffa3441e98ed2d8482a9560f0 +timeCreated: 1726477905 \ No newline at end of file diff --git a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs index eb64da49d..0363dc26c 100644 --- a/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs +++ b/Editor/Processors/TraceAndOptimize/OptimizeTexture.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using Anatawa12.AvatarOptimizer.AnimatorParsersV2; +using Anatawa12.AvatarOptimizer.API; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; using nadena.dev.ndmf; using UnityEditor; @@ -175,11 +176,13 @@ BuildContext context foreach (var (material, _) in materialUsers) { - var provider = new TextureUsageInformationCallback( + var provider = new TextureUsageInformationCallbackImpl( material, materialUsers[material].Select(x => context.GetAnimationComponent(x.MeshInfo2.SourceRenderer)) .ToList()); - if (ShaderKnowledge.GetTextureUsageInformationForMaterial(provider) && provider.TextureUsageInformations is {} informations) + if (ShaderInformationRegistry.GetShaderInformation(material.shader) is {} information + && information.GetTextureUsageInformationForMaterial(provider) + && provider.TextureUsageInformations is {} informations) usageInformations.Add(material, informations.ToArray()); else unmergeableMaterials.Add(material); @@ -391,7 +394,7 @@ internal void DoAtlas(IEnumerable<(Material, TextureUsageInformation[], HashSet< return (safeToMerge: true, Array.Empty()); } - class TextureUsageInformationCallback : ShaderKnowledge.TextureUsageInformationCallback + class TextureUsageInformationCallbackImpl : TextureUsageInformationCallback { private readonly Material _material; private readonly List> _infos; @@ -399,13 +402,13 @@ class TextureUsageInformationCallback : ShaderKnowledge.TextureUsageInformationC public List? TextureUsageInformations => _textureUsageInformations; - public TextureUsageInformationCallback(Material material, List> infos) + public TextureUsageInformationCallbackImpl(Material material, List> infos) { _material = material; _infos = infos; } - public override Shader Shader => _material.shader; + public Shader Shader => _material.shader; private T? GetValue(string propertyName, Func computer, bool considerAnimation) where T : struct { @@ -421,7 +424,7 @@ public TextureUsageInformationCallback(Material material, List GetValue(propertyName, _material.GetVector, considerAnimation); - public override void RegisterOtherUVUsage(ShaderKnowledge.UsingUVChannels uvChannel) + public override void RegisterOtherUVUsage(UsingUVChannels uvChannel) { // no longer atlasing is not supported _textureUsageInformations = null; @@ -429,39 +432,39 @@ public override void RegisterOtherUVUsage(ShaderKnowledge.UsingUVChannels uvChan public override void RegisterTextureUVUsage( string textureMaterialPropertyName, - ShaderKnowledge.SamplerStateInformation samplerState, - ShaderKnowledge.UsingUVChannels uvChannels, + SamplerStateInformation samplerState, + UsingUVChannels uvChannels, UnityEngine.Matrix4x4? uvMatrix) { if (_textureUsageInformations == null) return; UVChannel uvChannel; switch (uvChannels) { - case ShaderKnowledge.UsingUVChannels.NonMesh: + case UsingUVChannels.NonMesh: uvChannel = UVChannel.NonMeshRelated; break; - case ShaderKnowledge.UsingUVChannels.UV0: + case UsingUVChannels.UV0: uvChannel = UVChannel.UV0; break; - case ShaderKnowledge.UsingUVChannels.UV1: + case UsingUVChannels.UV1: uvChannel = UVChannel.UV1; break; - case ShaderKnowledge.UsingUVChannels.UV2: + case UsingUVChannels.UV2: uvChannel = UVChannel.UV2; break; - case ShaderKnowledge.UsingUVChannels.UV3: + case UsingUVChannels.UV3: uvChannel = UVChannel.UV3; break; - case ShaderKnowledge.UsingUVChannels.UV4: + case UsingUVChannels.UV4: uvChannel = UVChannel.UV4; break; - case ShaderKnowledge.UsingUVChannels.UV5: + case UsingUVChannels.UV5: uvChannel = UVChannel.UV5; break; - case ShaderKnowledge.UsingUVChannels.UV6: + case UsingUVChannels.UV6: uvChannel = UVChannel.UV6; break; - case ShaderKnowledge.UsingUVChannels.UV7: + case UsingUVChannels.UV7: uvChannel = UVChannel.UV7; break; default: diff --git a/Editor/ShaderKnowledge.cs b/Editor/ShaderKnowledge.cs index cc96f250f..2b15eb17e 100644 --- a/Editor/ShaderKnowledge.cs +++ b/Editor/ShaderKnowledge.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; using System.Linq; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; -using UnityEditor; using UnityEngine; namespace Anatawa12.AvatarOptimizer @@ -99,771 +96,5 @@ private static bool CheckAffectedUvDiscard(Material material, MeshInfo2 meshInfo return x == column && y == row; }); } - - public abstract class TextureUsageInformationCallback - { - internal TextureUsageInformationCallback() { } - - public abstract Shader Shader { get; } - - /// - /// Returns the integer value for the property in the material, or null if the property is not set or not found. - /// - /// The name of the property in the material. - /// Whether to consider the animation of the property. If this is true, this function will never - /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. - public abstract int? GetInteger(string propertyName, bool considerAnimation = true); - - /// - /// Returns the float value for the property in the material, or null if the property is not set or not found. - /// - /// The name of the property in the material. - /// Whether to consider the animation of the property. If this is true, this function will never - /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. - public abstract float? GetFloat(string propertyName, bool considerAnimation = true); - - /// - /// Returns the float value for the property in the material, or null if the property is not set or not found. - /// - /// The name of the property in the material. - /// Whether to consider the animation of the property. If this is true, this function will never - /// The integer value for the property in the material, which is same as , or null if the property is not set or not found. - public abstract Vector4? GetVector(string propertyName, bool considerAnimation = true); - - /// - /// Registers UV Usage that are not considered by Avatar Optimizer. - /// - /// This will the UV Channel not affected by optimizations of Avatar Optimizer. - /// - /// The UVChannels that are used in the shader. - public abstract void RegisterOtherUVUsage(UsingUVChannels uvChannel); - - /// - /// Registers Texture Usage and UV Usage that are considered by Avatar Optimizer. - /// - /// The texture might go to the atlas / UV Packing if the UsingUVChannels is set and the UV Matrix is known - /// - /// The name of the texture property in the material. - /// The information about the sampler state used for the specified texture. - /// The UVChannels that are used in the shader to determine the UV for the texture. - /// The UV Transform Matrix for the texture. This includes textureName_ST scale offset. Null if the UV transfrom is not known. - /// - /// This section describes the current and planned implementation of UV Packing in the Avatar Optimizer about this function. - /// - /// Currently, Avatar Optimizer does UV Packing if (non-exclusive): - /// - Texture is reasonably used by small set of materials - /// - UsingUVChannels is set to only one of UV Channels (per material) - /// - UV Matrix is known and identity matrix - /// - /// However, Avatar Optimizer will support more complex UV Packing in the future: - /// - Support UV Matrix with scale is smaller and rotation is multiple of 90 degrees - /// - multiple UV Channel texture - /// - public abstract void RegisterTextureUVUsage( - string textureMaterialPropertyName, - SamplerStateInformation samplerState, - UsingUVChannels uvChannels, - UnityEngine.Matrix4x4? uvMatrix); - } - - /// - /// The information about the sampler state for the specified texture. - /// - /// You can combine multiple SamplerStateInformation for the texture with `|` operator. - /// - /// You can cast string to SamplerStateInformation to use the sampler state for - /// the specified texture like sampler_MainTex by (SamplerStateInformation)"_MainTex". - /// - /// If your shader is using hardcoded sampler state, you can use the predefined sampler state like - /// or . - /// - internal readonly struct SamplerStateInformation - { - private readonly string _textureName; - private readonly bool _materialProperty; - - public SamplerStateInformation(string textureName) - { - _textureName = textureName; - _materialProperty = true; - } - - // construct builtin non-material property sampler state - private SamplerStateInformation(string textureName, bool dummy) - { - _textureName = textureName; - _materialProperty = false; - } - - // I don't want to expose equals to public API so I made this internal function instead of overriding Equals - internal static bool EQ(SamplerStateInformation left, SamplerStateInformation right) - { - if (left._materialProperty != right._materialProperty) return false; - if (left._textureName != right._textureName) return false; - return true; - } - - public static readonly SamplerStateInformation Unknown = new("Unknown", false); - public static readonly SamplerStateInformation PointClampSampler = new("PointClamp", false); - public static readonly SamplerStateInformation PointRepeatSampler = new("PointRepeat", false); - public static readonly SamplerStateInformation PointMirrorSampler = new("PointMirror", false); - public static readonly SamplerStateInformation PointMirrorOnceSampler = new("PointMirrorOnce", false); - public static readonly SamplerStateInformation LinearClampSampler = new("LinearClamp", false); - public static readonly SamplerStateInformation LinearRepeatSampler = new("LinearRepeat", false); - public static readonly SamplerStateInformation LinearMirrorSampler = new("LinearMirror", false); - public static readonly SamplerStateInformation LinearMirrorOnceSampler = new("LinearMirrorOnce", false); - public static readonly SamplerStateInformation TrilinearClampSampler = new("TrilinearClamp", false); - public static readonly SamplerStateInformation TrilinearRepeatSampler = new("TrilinearRepeat", false); - public static readonly SamplerStateInformation TrilinearMirrorSampler = new("TrilinearMirror", false); - public static readonly SamplerStateInformation TrilinearMirrorOnceSampler = new("TrilinearMirrorOnce", false); - - public static implicit operator SamplerStateInformation(string textureName) => new(textureName); - public static SamplerStateInformation operator|(SamplerStateInformation left, SamplerStateInformation right) => - Combine(left, right); - - private static SamplerStateInformation Combine(SamplerStateInformation left, SamplerStateInformation right) - { - // we may implement better logic in the future - if (EQ(left, right)) return left; - return Unknown; - } - } - - /// - /// The flags to express which UV Channels might be used in the shader. - /// - /// Usage of the UV channels might be specified with some other APIs. - /// - [Flags] - public enum UsingUVChannels - { - UV0 = 1, - UV1 = 2, - UV2 = 4, - UV3 = 8, - UV4 = 16, - UV5 = 32, - UV6 = 64, - UV7 = 128, - NonMesh = 256, - Unknown = 0x7FFFFFFF, - } - - /// - /// Returns texture usage information for the material. - /// - /// null if the shader is not supported - public static bool GetTextureUsageInformationForMaterial(TextureUsageInformationCallback information) - { - if (AssetDatabase.GetAssetPath(information.Shader).StartsWith("Packages/jp.lilxyzw.liltoon")) - { - // it looks liltoon! - return GetTextureUsageInformationForMaterialLiltoon(information); - } - - return false; - } - - private static bool GetTextureUsageInformationForMaterialLiltoon(TextureUsageInformationCallback matInfo) - { - // This implementation is made for my Anon + Wahuku for testing this feature. - // TODO: version check - - var uvMain = UsingUVChannels.UV0; - var uvMainScaleOffset = "_MainTex_ST"; - UnityEngine.Matrix4x4? uvMainMatrix = ComputeUVMainMatrix(); - - UnityEngine.Matrix4x4? ComputeUVMainMatrix() - { - // _ShiftBackfaceUV - if (matInfo.GetFloat("_ShiftBackfaceUV") != 0) return null; // changed depends on face - return STAndScrollRotateToMatrix("_MainTex_ST", "_MainTex_ScrollRotate"); - } - - matInfo.RegisterTextureUVUsage("_DitherTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh, null); // dither UV is based on screen space - - // TODO: _MainTex with POM / PARALLAX (using LIL_SAMPLE_2D_POM) - LIL_SAMPLE_2D_WithMat("_MainTex", "_MainTex", uvMain, uvMainMatrix); // main texture - matInfo.RegisterTextureUVUsage("_MainGradationTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); // GradationMap UV is based on color - LIL_SAMPLE_2D_WithMat("_MainColorAdjustMask", "_MainTex", uvMain, uvMainMatrix); // simple LIL_SAMPLE_2D - - if (matInfo.GetInteger("_UseMain2ndTex") != 0) - { - // caller of lilGetMain2nd will pass sampler for _MainTex as samp - SamplerStateInformation samp = "_MainTex"; - - UsingUVChannels uv2nd; - switch (matInfo.GetInteger("_Main2ndTex_UVMode")) - { - case 0: uv2nd = UsingUVChannels.UV0; break; - case 1: uv2nd = UsingUVChannels.UV1; break; - case 2: uv2nd = UsingUVChannels.UV2; break; - case 3: uv2nd = UsingUVChannels.UV3; break; - case 4: uv2nd = UsingUVChannels.NonMesh; break; // MatCap (normal-based UV) - default: uv2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; - } - LIL_GET_SUBTEX("_Main2ndTex", uv2nd); - LIL_SAMPLE_2D_WithMat("_Main2ndBlendMask", samp, uvMain, uvMainMatrix); - lilCalcDissolveWithOrWithoutNoise( - UsingUVChannels.UV0, - "_Main2ndDissolveMask", - "_Main2ndDissolveMask_ST", - "_Main2ndDissolveNoiseMask", - "_Main2ndDissolveNoiseMask_ST", - "_Main2ndDissolveNoiseMask_ScrollRotate", - samp - ); - } - - if (matInfo.GetInteger("_UseMain3rdTex") != 0) - { - // caller of lilGetMain3rd will pass sampler for _MainTex as samp - var samp = "_MainTex"; - - UsingUVChannels uv3rd; - switch (matInfo.GetInteger("_Main2ndTex_UVMode")) - { - case 0: uv3rd = UsingUVChannels.UV0; break; - case 1: uv3rd = UsingUVChannels.UV1; break; - case 2: uv3rd = UsingUVChannels.UV2; break; - case 3: uv3rd = UsingUVChannels.UV3; break; - case 4: uv3rd = UsingUVChannels.NonMesh; break; // MatCap (normal-based UV) - default: uv3rd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; - } - - LIL_GET_SUBTEX("_Main3rdTex", uv3rd); - LIL_SAMPLE_2D_WithMat("_Main3rdBlendMask", samp, uvMain, uvMainMatrix); - lilCalcDissolveWithOrWithoutNoise( - UsingUVChannels.UV0, - "_Main3rdDissolveMask", - "_Main3rdDissolveMask_ST", - "_Main3rdDissolveNoiseMask", - "_Main3rdDissolveNoiseMask_ST", - "_Main3rdDissolveNoiseMask_ScrollRotate", - samp - ); - } - - LIL_SAMPLE_2D_ST_WithMat("_AlphaMask", "_MainTex", uvMain, uvMainMatrix); - if (matInfo.GetInteger("_UseBumpMap") != 0) - { - LIL_SAMPLE_2D_ST_WithMat("_BumpMap", "_MainTex", uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_UseBump2ndMap") != 0) - { - var uvBump2nd = UsingUVChannels.UV0; - - switch (matInfo.GetInteger("_Bump2ndMap_UVMode")) - { - case 0: uvBump2nd = UsingUVChannels.UV0; break; - case 1: uvBump2nd = UsingUVChannels.UV1; break; - case 2: uvBump2nd = UsingUVChannels.UV2; break; - case 3: uvBump2nd = UsingUVChannels.UV3; break; - case null: uvBump2nd = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3; break; - } - - LIL_SAMPLE_2D_ST("_Bump2ndMap", SamplerStateInformation.LinearRepeatSampler, uvBump2nd); - LIL_SAMPLE_2D_ST_WithMat("_Bump2ndScaleMask", "_MainTex", uvMain, uvMainMatrix); - - // Note: _Bump2ndScaleMask is defined as NoScaleOffset but sampled with LIL_SAMPLE_2D_ST? - } - - if (matInfo.GetInteger("_UseAnisotropy") != 0) - { - LIL_SAMPLE_2D_ST_WithMat("_AnisotropyTangentMap", "_MainTex", uvMain, uvMainMatrix); - LIL_SAMPLE_2D_ST_WithMat("_AnisotropyScaleMask", "_MainTex", uvMain, uvMainMatrix); - - // _AnisotropyShiftNoiseMask is used in another place but under _UseAnisotropy condition - LIL_SAMPLE_2D_ST_WithMat("_AnisotropyShiftNoiseMask", "_MainTex", uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_UseBacklight") != 0) - { - var samp = "_MainTex"; - LIL_SAMPLE_2D_ST_WithMat("_BacklightColorTex", samp, uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_UseShadow") != 0) - { - SamplerStateInformation samp = "_MainTex"; - LIL_SAMPLE_2D_GRAD_WithMat("_ShadowStrengthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBorderMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_GRAD_WithMat("_ShadowBlurMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); - // lilSampleLUT - switch (matInfo.GetInteger("_ShadowColorType")) - { - case 1: - LIL_SAMPLE_2D_WithMat("_ShadowColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); - LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); - LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", SamplerStateInformation.LinearClampSampler, UsingUVChannels.NonMesh, null); - break; - case null: - var sampler = samp | SamplerStateInformation.LinearClampSampler; - LIL_SAMPLE_2D_WithMat("_ShadowColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); - LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); - LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", sampler, UsingUVChannels.NonMesh | uvMain, null); - break; - default: - LIL_SAMPLE_2D_WithMat("_ShadowColorTex", samp, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_WithMat("_Shadow2ndColorTex", samp, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_WithMat("_Shadow3rdColorTex", samp, uvMain, uvMainMatrix); - break; - } - } - - if (matInfo.GetInteger("_UseRimShade") != 0) - { - var samp = "_MainTex"; - - LIL_SAMPLE_2D_WithMat("_RimShadeMask", samp, uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_UseReflection") != 0) - { - // TODO: research - var samp = "_MainTex"; // or SamplerStateInformation.LinearRepeatSampler in lil_pass_foreward_reblur.hlsl - - LIL_SAMPLE_2D_ST_WithMat("_SmoothnessTex", samp, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_ST_WithMat("_MetallicGlossMap", samp, uvMain, uvMainMatrix); - LIL_SAMPLE_2D_ST_WithMat("_ReflectionColorTex", samp, uvMain, uvMainMatrix); - } - - // Matcap - if (matInfo.GetInteger("_UseMatCap") != 0) - { - var samp = "_MainTex"; // caller of lilGetMatCap - - LIL_SAMPLE_2D("_MatCapTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); - LIL_SAMPLE_2D_ST_WithMat("_MatCapBlendMask", samp, uvMain, uvMainMatrix); - - if (matInfo.GetInteger("_MatCapCustomNormal") != 0) - { - LIL_SAMPLE_2D_ST_WithMat("_MatCapBumpMap", samp, uvMain, uvMainMatrix); - } - } - - if (matInfo.GetInteger("_UseMatCap2nd") != 0) - { - var samp = "_MainTex"; // caller of lilGetMatCap - - LIL_SAMPLE_2D("_MatCap2ndTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); - LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBlendMask", samp, uvMain, uvMainMatrix); - - if (matInfo.GetInteger("_MatCap2ndCustomNormal") != 0) - { - LIL_SAMPLE_2D_ST_WithMat("_MatCap2ndBumpMap", samp, uvMain, uvMainMatrix); - } - } - - // rim light - if (matInfo.GetInteger("_UseRim") != 0) - { - var samp = "_MainTex"; // caller of lilGetRim - LIL_SAMPLE_2D_ST_WithMat("_RimColorTex", samp, uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_UseGlitter") != 0) - { - var samp = "_MainTex"; // caller of lilGetGlitter - - LIL_SAMPLE_2D_ST_WithMat("_GlitterColorTex", samp, uvMain, uvMainMatrix); - if (matInfo.GetInteger("_GlitterApplyShape") != 0) - { - // complex uv - LIL_SAMPLE_2D_GRAD("_GlitterShapeTex", SamplerStateInformation.LinearClampSampler, - UsingUVChannels.NonMesh); - } - } - - if (matInfo.GetInteger("_UseEmission") != 0) - { - UsingUVChannels emissionUV = UsingUVChannels.UV0; - - switch (matInfo.GetInteger("_EmissionMap_UVMode")) - { - case 1: emissionUV = UsingUVChannels.UV1; break; - case 2: emissionUV = UsingUVChannels.UV2; break; - case 3: emissionUV = UsingUVChannels.UV3; break; - case 4: emissionUV = UsingUVChannels.NonMesh; break; // uvRim; TODO: check - case null: emissionUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3 | UsingUVChannels.NonMesh; break; - } - - var parallaxEnabled = matInfo.GetFloat("_EmissionParallaxDepth") != 0; - - LIL_GET_EMITEX("_EmissionMap", emissionUV, parallaxEnabled); - - // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. - var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); - - if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) - { - LIL_GET_EMIMASK("_EmissionBlendMask", UsingUVChannels.UV0); - } - else - { - LIL_GET_EMIMASK_WithMat("_EmissionBlendMask", uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_EmissionUseGrad") != 0) - { - LIL_SAMPLE_1D("_EmissionGradTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); - } - } - - if (matInfo.GetInteger("_Emission2ndMap") != 0) - { - UsingUVChannels emission2ndUV = UsingUVChannels.UV0; - - switch (matInfo.GetInteger("_Emission2ndMap_UVMode")) - { - case 1: emission2ndUV = UsingUVChannels.UV1; break; - case 2: emission2ndUV = UsingUVChannels.UV2; break; - case 3: emission2ndUV = UsingUVChannels.UV3; break; - case 4: emission2ndUV = UsingUVChannels.NonMesh; break; // uvRim; TODO: check - case null: emission2ndUV = UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3 | UsingUVChannels.NonMesh; break; - } - - var parallaxEnabled = matInfo.GetFloat("_Emission2ndParallaxDepth") != 0; - - // actually LIL_GET_EMITEX is used but same as LIL_SAMPLE_2D_ST - LIL_GET_EMITEX("_Emission2ndMap", emission2ndUV, parallaxEnabled); - - // if LIL_FEATURE_ANIMATE_EMISSION_MASK_UV is enabled, UV0 is used and if not UVMain is used. (weird) - // https://github.com/lilxyzw/lilToon/blob/b96470d3dd9092b840052578048b2307fe6d8786/Assets/lilToon/Shader/Includes/lil_common_frag.hlsl#L1819-L1821 - var LIL_FEATURE_ANIMATE_EMISSION_MASK_UV = matInfo.GetVector("_EmissionBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0) || matInfo.GetVector("_Emission2ndBlendMask_ScrollRotate") != new Vector4(0, 0, 0, 0); - - if (LIL_FEATURE_ANIMATE_EMISSION_MASK_UV) - { - LIL_GET_EMIMASK("_Emission2ndBlendMask", UsingUVChannels.UV0); - } - else - { - LIL_GET_EMIMASK_WithMat("_Emission2ndBlendMask", uvMain, uvMainMatrix); - } - - if (matInfo.GetInteger("_Emission2ndUseGrad") != 0) - { - LIL_SAMPLE_1D("_Emission2ndGradTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.NonMesh); - } - } - - if (matInfo.GetInteger("_UseParallax") != 0) - { - matInfo.RegisterTextureUVUsage("_ParallaxMap", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV0, null); - } - - if (matInfo.GetInteger("_UseAudioLink") != 0 && matInfo.GetInteger("_AudioLink2Vertex") != 0) - { - var _AudioLinkUVMode = matInfo.GetInteger("_AudioLinkUVMode"); - - if (_AudioLinkUVMode is 3 or 4 or null) - { - // TODO: _AudioLinkMask_ScrollRotate - var sampler = "_AudioLinkMask" | SamplerStateInformation.LinearRepeatSampler; - switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) - { - case 0: - default: - LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, uvMain, uvMainMatrix); - break; - case 1: - LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV1); - break; - case 2: - LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV2); - break; - case 3: - LIL_SAMPLE_2D_ST("_AudioLinkMask", sampler, UsingUVChannels.UV3); - break; - case null: - LIL_SAMPLE_2D_ST_WithMat("_AudioLinkMask", sampler, - uvMain | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, - Combine(uvMainMatrix, Matrix4x4.identity)); - break; - } - } - } - - if (matInfo.GetVector("_DissolveParams")?.x != 0) - { - lilCalcDissolveWithOrWithoutNoise( - //fd.col.a, - //dissolveAlpha, - UsingUVChannels.UV0, - //fd.positionOS, - //_DissolveParams, - //_DissolvePos, - "_DissolveMask", - "_DissolveMask_ST", - //_DissolveMaskEnabled, - "_DissolveNoiseMask", - "_DissolveNoiseMask_ST", - "_DissolveNoiseMask_ScrollRotate", - //_DissolveNoiseStrength - "_MainTex" - ); - } - - if (matInfo.GetInteger("_UseOutline") != 0) { // not on material side, on editor side toggle - LIL_SAMPLE_2D_WithMat("_OutlineTex", "_OutlineTex", uvMain, uvMainMatrix); - LIL_SAMPLE_2D_WithMat("_OutlineWidthMask", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); - // _OutlineVectorTex SamplerStateInformation.LinearRepeatSampler - // UVs _OutlineVectorUVMode main,1,2,3 - - switch (matInfo.GetInteger("_AudioLinkMask_UVMode")) - { - case 0: - LIL_SAMPLE_2D_WithMat("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, uvMain, uvMainMatrix); - break; - case 1: - LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV1); - break; - case 2: - LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV2); - break; - case 3: - LIL_SAMPLE_2D("_OutlineVectorTex", SamplerStateInformation.LinearRepeatSampler, UsingUVChannels.UV3); - break; - default: - case null: - matInfo.RegisterTextureUVUsage( - "_OutlineVectorTex", - SamplerStateInformation.LinearRepeatSampler, - UsingUVChannels.UV0 | UsingUVChannels.UV1 | UsingUVChannels.UV2 | UsingUVChannels.UV3, - Combine(uvMainMatrix, UnityEngine.Matrix4x4.identity) - ); - break; - } - } - - // _BaseMap and _BaseColorMap are unused - - return true; - - void LIL_SAMPLE_1D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) - { - matInfo.RegisterTextureUVUsage( - textureName, - samplerName, - uvChannel, - UnityEngine.Matrix4x4.identity - ); - } - - void LIL_SAMPLE_2D(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) - { - // might be _LOD: using SampleLevel - matInfo.RegisterTextureUVUsage( - textureName, - samplerName, - uvChannel, - UnityEngine.Matrix4x4.identity - ); - } - - void LIL_SAMPLE_2D_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) - { - // might be _LOD: using SampleLevel - matInfo.RegisterTextureUVUsage( - textureName, - samplerName, - uvChannel, - matrix - ); - } - - void LIL_SAMPLE_2D_GRAD(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) - { - // additional parameter for SampleGrad does not affect UV location much - LIL_SAMPLE_2D(textureName, samplerName, uvChannel); - } - - void LIL_SAMPLE_2D_GRAD_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) - { - // additional parameter for SampleGrad does not affect UV location much - LIL_SAMPLE_2D_WithMat(textureName, samplerName, uvChannel, matrix); - } - - void LIL_SAMPLE_2D_ST(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel) - { - matInfo.RegisterTextureUVUsage( - textureName, - samplerName, - uvChannel, - STToMatrix($"{textureName}_ST") - ); - } - - void LIL_SAMPLE_2D_ST_WithMat(string textureName, SamplerStateInformation samplerName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) - { - matInfo.RegisterTextureUVUsage( - textureName, - samplerName, - uvChannel, - Multiply(STToMatrix($"{textureName}_ST"), matrix) - ); - } - - void LIL_GET_SUBTEX(string textureName, UsingUVChannels uvChannel) - { - // lilGetSubTex - - // TODO: consider the following properties - var st = $"{textureName}_ST"; - var scrollRotate = $"{textureName}_ScrollRotate"; - var angle = $"{textureName}Angle"; - var isDecal = $"{textureName}IsDecal"; - var isLeftOnly = $"{textureName}IsLeftOnly"; - var isRightOnly = $"{textureName}IsRightOnly"; - var shouldCopy = $"{textureName}ShouldCopy"; - var shouldFlipMirror = $"{textureName}ShouldFlipMirror"; - var shouldFlipCopy = $"{textureName}ShouldFlipCopy"; - var isMSDF = $"{textureName}IsMSDF"; - var decalAnimation = $"{textureName}DecalAnimation"; - var decalSubParam = $"{textureName}DecalSubParam"; - // fd.nv? - // fd.isRightHand? - - - Matrix4x4? ComputeMatrix() - { - var stValueOpt = matInfo.GetVector(st); - var rotateValueOpt = matInfo.GetVector(scrollRotate); - var angleValueOpt = matInfo.GetFloat(angle); - //var isDecalValueOpt = matInfo.GetFloat(isDecal); - var isLeftOnlyValueOpt = matInfo.GetFloat(isLeftOnly); - var isRightOnlyValueOpt = matInfo.GetFloat(isRightOnly); - var shouldCopyValueOpt = matInfo.GetFloat(shouldCopy); - var shouldFlipMirrorValueOpt = matInfo.GetFloat(shouldFlipMirror); - var shouldFlipCopyValueOpt = matInfo.GetFloat(shouldFlipCopy); - //var isMSDFValueOpt = matInfo.GetFloat(isMSDF); - var decalAnimationValueOpt = matInfo.GetVector(decalAnimation); - // var decalSubParamValueOpt = matInfo.GetVector(decalSubParam); - - if (stValueOpt is not { } stValue) return null; - if (rotateValueOpt is not { } rotateValue) return null; - if (angleValueOpt is not { } angleValue) return null; - - rotateValue.z = angleValue; - - if (STAndScrollRotateValueToMatrix(stValue, rotateValue) is not { } matrix) return null; - - // shouldCopy is true => x = abs(x - 0.5) + 0.5 - if (shouldCopyValueOpt != 0) return null; - // shouldFlipCopy is true => flips - if (shouldFlipCopyValueOpt != 0) return null; - // shouldFlipMirror is true => flips - if (shouldFlipMirrorValueOpt != 0) return null; - - // isDecal is true => decal - if (isLeftOnlyValueOpt != 0) return null; - if (isRightOnlyValueOpt != 0) return null; - - // rotation is performed in STAndScrollRotateValueToMatrix - - if (decalAnimationValueOpt != new Vector4(1.0f, 1.0f, 1.0f, 30.0f)) return null; - - return matrix; - } - - matInfo.RegisterTextureUVUsage(textureName, textureName, uvChannel, ComputeMatrix()); - } - - void LIL_GET_EMITEX(string textureName, UsingUVChannels uvChannel, bool parallaxEnabled) - { - LIL_SAMPLE_2D_WithMat(textureName, textureName, uvChannel, - parallaxEnabled ? null : STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); - } - - void LIL_GET_EMIMASK_WithMat(string textureName, UsingUVChannels uvChannel, UnityEngine.Matrix4x4? matrix) - { - LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, - Multiply(matrix, STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate"))); - } - - void LIL_GET_EMIMASK(string textureName, UsingUVChannels uvChannel) - { - LIL_SAMPLE_2D_WithMat(textureName, "_MainTex", uvChannel, - STAndScrollRotateToMatrix($"{textureName}_ST", $"{textureName}_ScrollRotate")); - } - - void lilCalcDissolveWithOrWithoutNoise( - // alpha, - // dissolveAlpha, - UsingUVChannels uv, // ? - // positionOS, - // dissolveParams, - // dissolvePos, - string dissolveMask, - string dissolveMaskST, - // dissolveMaskEnabled - string dissolveNoiseMask, - string dissolveNoiseMaskST, - string dissolveNoiseMaskScrollRotate, - // dissolveNoiseStrength, - SamplerStateInformation samp - ) - { - LIL_SAMPLE_2D_WithMat(dissolveMask, samp, uv, STToMatrix(dissolveMaskST)); - LIL_SAMPLE_2D_WithMat(dissolveNoiseMask, samp, uv, STAndScrollRotateToMatrix(dissolveNoiseMaskST, dissolveNoiseMaskScrollRotate)); - } - - // lilCalcUV - Matrix4x4? STToMatrix(string stPropertyName) => STValueToMatrix(matInfo.GetVector(stPropertyName)); - - Matrix4x4? STValueToMatrix(Vector4? stIn) - { - if (stIn is not { } st) return null; - - var matrix = Matrix4x4.identity; - matrix.m00 = st.x; - matrix.m11 = st.y; - matrix.m03 = st.z; - matrix.m13 = st.w; - - return matrix; - } - - // lilCalcUV - Matrix4x4? STAndScrollRotateToMatrix(string stPropertyName, string scrollRotatePropertyName) => - STAndScrollRotateValueToMatrix(matInfo.GetVector(stPropertyName), matInfo.GetVector(scrollRotatePropertyName)); - - Matrix4x4? STAndScrollRotateValueToMatrix(Vector4? stValueIn, Vector4? scrollRotateIn) - { - if (STValueToMatrix(stValueIn) is not { } stMatrix) return null; - if (scrollRotateIn is not { } scrollRotate) return stMatrix; - - float staticAngle = scrollRotate.z; - float scrollAngleSpeed = scrollRotate.w; - Vector2 scrollSpeed = new(scrollRotate.x, scrollRotate.y); - - if (scrollSpeed != Vector2.zero || scrollAngleSpeed != 0) return null; - - if (staticAngle == 0) return stMatrix; - - var result = stMatrix; - - result *= Matrix4x4.TRS(new Vector3(-0.5f, -0.5f), Quaternion.identity, Vector3.one); - result *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, staticAngle), Vector3.one); - result *= Matrix4x4.TRS(new Vector3(0.5f, 0.5f), Quaternion.identity, Vector3.one); - - return result; - } - - static Matrix4x4? Combine(Matrix4x4? a, Matrix4x4? b) - { - if (a == null) return b; - if (b == null) return a; - if (a == b) return a; - return null; - } - - Matrix4x4? Multiply(Matrix4x4? a, Matrix4x4? b) - { - if (a == null || b == null) return null; - return a.Value * b.Value; - } - } } } From 97beb8d48303279271fabe1009d97cd8903b2801 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 16 Sep 2024 21:31:57 +0900 Subject: [PATCH 30/30] docs(changelog): Optimize Texture in Trace nad Optimize --- CHANGELOG-PRERELEASE.md | 2 ++ CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index cf6f49dcb..a00ee8509 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog]. - We may relax some restriction in the future. - Because we have to check for each condition if we use AnyState but we can check for only one (in best case) with entry/exit, this generally reduces cost for checking an parameter in a state. - Combined with Entry / Exit to 1D BlendTree optimization, which is implemented in previous release, your AnyState layer may be optimized to 1D BlendTree. +- Optimize Texture in Trace nad Optimize `#1181` + - Avatar Optimizer will pack texture and tries to reduce the VRAM usage. ### Changed - Skip Enablement Mismatched Renderers is now disabled by default `#1169` diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b085771a..e71989399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog]. - We may relax some restriction in the future. - Because we have to check for each condition if we use AnyState but we can check for only one (in best case) with entry/exit, this generally reduces cost for checking an parameter in a state. - Combined with Entry / Exit to 1D BlendTree optimization, which is implemented in previous release, your AnyState layer may be optimized to 1D BlendTree. +- Optimize Texture in Trace nad Optimize `#1181` + - Avatar Optimizer will pack texture and tries to reduce the VRAM usage. ### Changed - Skip Enablement Mismatched Renderers is now disabled by default `#1169`