From bdd8e0857235c56b7013fe768a333c5575aaa0ef Mon Sep 17 00:00:00 2001 From: mob-sakai <12690315+mob-sakai@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:01:18 +0900 Subject: [PATCH] feat: on-demand UIEffect shader support close #212, close #271 --- .../UIEffectProjectSettings.asset | 7 +- .../Editor/UIEffectProjectSettingsEditor.cs | 100 +---- Packages/src/README.md | 35 +- .../Extensions/TransformExtensions.cs | 144 ------ .../PreloadedProjectSettings.cs | 11 +- .../Utilities/ShaderVariantRegistry.cs | 410 ++++++++++++++++++ .../ShaderVariantRegistry.cs.meta} | 2 +- .../Utilities/TransformSensitivity.cs | 82 ++++ .../Utilities/TransformSensitivity.cs.meta | 11 + .../Internal/Utilities/UIExtraCallbacks.cs | 2 +- Packages/src/Runtime/UIEffectBase.cs | 38 +- .../src/Runtime/UIEffectProjectSettings.cs | 115 +---- Packages/src/Runtime/UIEffectReplica.cs | 2 +- 13 files changed, 577 insertions(+), 382 deletions(-) delete mode 100644 Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs create mode 100644 Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs rename Packages/src/Runtime/Internal/{Extensions/TransformExtensions.cs.meta => Utilities/ShaderVariantRegistry.cs.meta} (83%) create mode 100644 Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs create mode 100644 Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs.meta diff --git a/Assets/ProjectSettings/UIEffectProjectSettings.asset b/Assets/ProjectSettings/UIEffectProjectSettings.asset index ba3ae7f8..7ba5b258 100644 --- a/Assets/ProjectSettings/UIEffectProjectSettings.asset +++ b/Assets/ProjectSettings/UIEffectProjectSettings.asset @@ -300,6 +300,9 @@ MonoBehaviour: m_RuntimePresets: - {fileID: 1645081683573595507, guid: 3b09ad143990c4c8795c6e93760e132a, type: 3} - {fileID: 1645081683573595507, guid: 4f70ff0f1e25b4388b9bcb38d619f7bf, type: 3} - m_FallbackVariantBehaviour: 0 - m_UnregisteredShaderVariants: [] m_ShaderVariantCollection: {fileID: -8740588384831342173} + m_ShaderVariantRegistry: + m_ErrorOnUnregisteredVariant: 0 + m_OptionalShaders: [] + m_UnregisteredVariants: [] + m_Asset: {fileID: -8740588384831342173} diff --git a/Packages/src/Editor/UIEffectProjectSettingsEditor.cs b/Packages/src/Editor/UIEffectProjectSettingsEditor.cs index 8c7895b2..04c1b1ff 100644 --- a/Packages/src/Editor/UIEffectProjectSettingsEditor.cs +++ b/Packages/src/Editor/UIEffectProjectSettingsEditor.cs @@ -1,8 +1,6 @@ ๏ปฟusing UnityEditor; using UnityEngine; -using System; -using System.Collections.Generic; -using System.Reflection; +using Coffee.UIEffectInternal; using UnityEditorInternal; namespace Coffee.UIEffects.Editors @@ -11,16 +9,14 @@ namespace Coffee.UIEffects.Editors public class UIEffectProjectSettingsEditor : Editor { private ReorderableList _reorderableList; - private SerializedProperty _fallbackVariantBehaviour; private SerializedProperty _transformSensitivity; - private Editor _editor; private bool _isInitialized; + private ShaderVariantRegistryEditor _shaderVariantRegistryEditor; private void InitializeIfNeeded() { if (_isInitialized) return; - _fallbackVariantBehaviour = serializedObject.FindProperty("m_FallbackVariantBehaviour"); _transformSensitivity = serializedObject.FindProperty("m_TransformSensitivity"); var runtimePresets = serializedObject.FindProperty("m_RuntimePresets"); _reorderableList = new ReorderableList(serializedObject, runtimePresets, true, true, true, true); @@ -53,19 +49,11 @@ private void InitializeIfNeeded() menu.DropDown(rect); }; - var collection = serializedObject.FindProperty("m_ShaderVariantCollection").objectReferenceValue; - _editor = CreateEditor(collection); _isInitialized = true; } private void OnDisable() { - if (_editor) - { - DestroyImmediate(_editor); - } - - _editor = null; _isInitialized = false; } @@ -78,86 +66,18 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(_transformSensitivity); _reorderableList.DoLayoutList(); - // Editor - EditorGUILayout.PropertyField(_fallbackVariantBehaviour); - serializedObject.ApplyModifiedProperties(); - - // Shader - EditorGUILayout.Space(20); - EditorGUILayout.LabelField("Shader Variants", EditorStyles.boldLabel); - EditorGUILayout.Space(-12); - DrawUnregisteredShaderVariants(UIEffectProjectSettings.instance.m_UnregisteredShaderVariants); - DrawRegisteredShaderVariants(_editor); - GUILayout.FlexibleSpace(); - } - - private static void DrawUnregisteredShaderVariants(List variants) - { - if (variants.Count == 0) return; - + // Shader registry EditorGUILayout.Space(); - var array = variants.ToArray(); - var r = EditorGUILayout.GetControlRect(false, 20); - var rLabel = new Rect(r.x, r.y, r.width - 80, r.height); - EditorGUI.LabelField(rLabel, "Registered Shader Variants"); - - var rButtonClear = new Rect(r.x + r.width - 80, r.y + 2, 80, r.height - 4); - if (GUI.Button(rButtonClear, "Clear All", EditorStyles.miniButton)) + EditorGUILayout.LabelField("Shader", EditorStyles.boldLabel); + if (_shaderVariantRegistryEditor == null) { - UIEffectProjectSettings.ClearUnregisteredShaderVariants(); + var property = serializedObject.FindProperty("m_ShaderVariantRegistry"); + _shaderVariantRegistryEditor = new ShaderVariantRegistryEditor(property, "(UIEffect)"); } - EditorGUILayout.BeginVertical("RL Background"); - for (var i = 0; i < array.Length; i++) - { - var values = array[i].Split(';'); - r = EditorGUILayout.GetControlRect(); - var rShader = new Rect(r.x, r.y, 150, r.height); - EditorGUI.ObjectField(rShader, Shader.Find(values[0]), typeof(Shader), false); - - var rKeywords = new Rect(r.x + 150, r.y, r.width - 150 - 20, r.height); - EditorGUI.TextField(rKeywords, values[1]); - - var rButton = new Rect(r.x + r.width - 20 + 2, r.y, 20, r.height); - if (GUI.Button(rButton, EditorGUIUtility.IconContent("icons/toolbar plus.png"), "iconbutton")) - { - UIEffectProjectSettings.RegisterVariant(array[i]); - } - } - - EditorGUILayout.EndVertical(); - } - - private static void DrawRegisteredShaderVariants(Editor editor) - { - var collection = editor.target as ShaderVariantCollection; - if (collection == null) return; - - EditorGUILayout.Space(); - var r = EditorGUILayout.GetControlRect(false, 20); - var rLabel = new Rect(r.x, r.y, r.width - 80, r.height); - EditorGUI.LabelField(rLabel, "Registered Shader Variants"); - - var rButton = new Rect(r.x + r.width - 80, r.y + 2, 80, r.height - 4); - if (GUI.Button(rButton, "Clear All", EditorStyles.miniButton)) - { - collection.Clear(); - } - - EditorGUILayout.BeginVertical("RL Background"); - editor.serializedObject.Update(); - var shaders = editor.serializedObject.FindProperty("m_Shaders"); - for (var i = 0; i < shaders.arraySize; i++) - { - s_MiDrawShaderEntry.Invoke(editor, new object[] { i }); - } - - editor.serializedObject.ApplyModifiedProperties(); - EditorGUILayout.EndVertical(); + _shaderVariantRegistryEditor.Draw(); + GUILayout.FlexibleSpace(); + serializedObject.ApplyModifiedProperties(); } - - private static readonly MethodInfo s_MiDrawShaderEntry = - Type.GetType("UnityEditor.ShaderVariantCollectionInspector, UnityEditor") - ?.GetMethod("DrawShaderEntry", BindingFlags.NonPublic | BindingFlags.Instance); } } diff --git a/Packages/src/README.md b/Packages/src/README.md index ba0c2593..7307b55b 100644 --- a/Packages/src/README.md +++ b/Packages/src/README.md @@ -32,7 +32,7 @@ Combine various filters, such as grayscale, blur, and dissolve, to decorate your - [Install via UPM (with Package Manager UI)](#install-via-upm-with-package-manager-ui) - [Install via UPM (Manually)](#install-via-upm-manually) - [Install as Embedded Package](#install-as-embedded-package) - - [Additional Resource Imports](#additional-resource-imports) + - [Import Additional Resources](#import-additional-resources) - [๐Ÿ”„ Upgrading from v4 to v5](#-upgrading-from-v4-to-v5) - [๐Ÿš€ Usage](#-usage) - [Getting Started](#getting-started) @@ -122,16 +122,17 @@ _This package requires **Unity 2020.3 or later**._ ### Install as Embedded Package -1. Download a source code zip file from [Releases](https://github.com/mob-sakai/UIEffect/releases) and extract it. -2. Place `/Packages/src` directory in your project's `Packages` directory. +1. Download the `Source code (zip)` file from [Releases](https://github.com/mob-sakai/UIEffect/releases) and + extract it. +2. Move the `/Packages/src` directory into your project's `Packages` directory. ![](https://github.com/user-attachments/assets/187cbcbe-5922-4ed5-acec-cf19aa17d208) -- You can rename the `src` directory. -- If you want to fix bugs or add features, install it as an embedded package. -- To update the package, you need to re-download it and replace the contents. + - You can rename the `src` directory if needed. + - If you intend to fix bugs or add features, installing it as an embedded package is recommended. + - To update the package, re-download it and replace the existing contents. -### Additional Resource Imports +### Import Additional Resources -UIEffect includes additional resources to import. +Additional resources can be imported to extend functionality. - [๐Ÿ”„ Upgrading from v4 to v5](#-upgrading-from-v4-to-v5) - [Usage with TextMeshPro](#usage-with-textmeshpro) @@ -301,18 +302,26 @@ UIEffectProjectSettings.shaderVariantCollection.WarmUp(); ### Project Settings -![](https://github.com/user-attachments/assets/33b01665-0893-4460-a220-62f5f08b2eec) +![](https://github.com/user-attachments/assets/54dd42cf-099d-4fb1-b699-cad29bf211b6) You can adjust the project-wide settings for UIEffect. (`Edit > Project Settings > UI > UIEffect`) - **Transform Sensitivity**: `Low`, `Medium`, `High` - Set the sensitivity of the transformation when `Use Target Transform` is enabled in `UIEffectReplica` component. - **Runtime Presets**: A list of presets that can be loaded at runtime. Load them using `UIEffect.LoadPreset(presetName)` method. -- **Fallback Variant Behavior**: Specifies the behavior when an unregistered shader variant is used in the editor. - - `Register Variant`: Adds the variant to `Registered Variants` for runtime use. - - `LogError`: Outputs a error and adds it to `Unregistered Variants`. +- **Optional Shaders (UIEffect)**: A list of shaders that will be prioritized when a ui-effect shader is + requested. + - If the shader is included in the list, that shader will be used. + - If it is not in the list, the following shaders will be used in order: + - If the shader name contains `(UIEffect)`, that shader will be used. + - If `Hidden/ (UIEffect)` exists, that shader will be used. + - As a fallback, `UI/Default (UIEffect)` will be used. +- **Registered Variants**: A list of shader variants available at runtime. Use "-" button to remove unused variants, + reducing build time and file size. + - By default, the used ui-effect shaders will be included in the build. You can remove them if you don't need. - **Unregistered Variants**: A list of shader variants that are not registered. Use "+" button to add variants. -- **Registered Variants**: A list of shader variants available at runtime. Use "-" button to remove unused variants, reducing build time and file size. +- **Error On Unregistered Variant**: If enabled, an error will be displayed when an unregistered shader variant is used. + - The shader variant will be automatically added to the `Unregistered Variants` list.

diff --git a/Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs b/Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs deleted file mode 100644 index 7c70874b..00000000 --- a/Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Profiling; - -namespace Coffee.UIEffectInternal -{ - /// - /// Extension methods for Transform class. - /// - internal static class TransformExtensions - { - private const float k_DefaultEpsilon = 1f / (2 ^ 8); - private static readonly Vector3[] s_Corners = new Vector3[4]; - - /// - /// Compare the hierarchy index of one transform with another transform. - /// - public static int CompareHierarchyIndex(this Transform self, Transform other, Transform stopAt) - { - if (self == other) return 0; - - Profiler.BeginSample("(COF)[TransformExt] CompareHierarchyIndex > GetTransforms"); - var lTrs = self.GetTransforms(stopAt, ListPool.Rent()); - var rTrs = other.GetTransforms(stopAt, ListPool.Rent()); - Profiler.EndSample(); - - Profiler.BeginSample("(COF)[TransformExt] CompareHierarchyIndex > Calc"); - var loop = Mathf.Max(lTrs.Count, rTrs.Count); - var result = 0; - for (var i = 0; i < loop; ++i) - { - var selfIndex = 0 <= lTrs.Count - i - 1 ? lTrs[lTrs.Count - i - 1].GetSiblingIndex() : -1; - var otherIndex = 0 <= rTrs.Count - i - 1 ? rTrs[rTrs.Count - i - 1].GetSiblingIndex() : -1; - if (selfIndex == otherIndex) continue; - - result = selfIndex - otherIndex; - break; - } - - Profiler.EndSample(); - - Profiler.BeginSample("(COF)[TransformExt] CompareHierarchyIndex > Return"); - ListPool.Return(ref lTrs); - ListPool.Return(ref rTrs); - Profiler.EndSample(); - - return result; - } - - private static List GetTransforms(this Transform self, Transform stopAt, List results) - { - results.Clear(); - while (self != stopAt) - { - results.Add(self); - self = self.parent; - } - - return results; - } - - /// - /// Check if a transform has changed. - /// - public static bool HasChanged(this Transform self, ref Matrix4x4 prev, float epsilon = k_DefaultEpsilon) - { - return self.HasChanged(null, ref prev, epsilon); - } - - /// - /// Check if a transform has changed. - /// - public static bool HasChanged(this Transform self, Transform baseTransform, ref Matrix4x4 prev, - float epsilon = k_DefaultEpsilon) - { - if (!self) return false; - - var hash = baseTransform ? baseTransform.GetHashCode() : 0; - if (FrameCache.TryGet(self, nameof(HasChanged), hash, out bool result)) return result; - - var matrix = baseTransform - ? baseTransform.worldToLocalMatrix * self.localToWorldMatrix - : self.localToWorldMatrix; - var current = matrix * Matrix4x4.Scale(Vector3.one * 10000); - result = !Approximately(current, prev, epsilon); - FrameCache.Set(self, nameof(HasChanged), hash, result); - if (result) - { - prev = current; - } - - return result; - } - - private static bool Approximately(Matrix4x4 self, Matrix4x4 other, float epsilon = k_DefaultEpsilon) - { - for (var i = 0; i < 16; i++) - { - if (epsilon < Mathf.Abs(self[i] - other[i])) - { - return false; - } - } - - return true; - } - - public static Bounds GetRelativeBounds(this Transform self, Transform child) - { - if (!self || !child) - { - return new Bounds(Vector3.zero, Vector3.zero); - } - - var list = ListPool.Rent(); - child.GetComponentsInChildren(false, list); - if (list.Count == 0) - { - ListPool.Return(ref list); - return new Bounds(Vector3.zero, Vector3.zero); - } - - var max = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); - var min = new Vector3(float.MinValue, float.MinValue, float.MinValue); - var worldToLocalMatrix = self.worldToLocalMatrix; - for (var i = 0; i < list.Count; i++) - { - list[i].GetWorldCorners(s_Corners); - for (var j = 0; j < 4; j++) - { - var lhs = worldToLocalMatrix.MultiplyPoint3x4(s_Corners[j]); - max = Vector3.Min(lhs, max); - min = Vector3.Max(lhs, min); - } - } - - ListPool.Return(ref list); - - var rectTransformBounds = new Bounds(max, Vector3.zero); - rectTransformBounds.Encapsulate(min); - return rectTransformBounds; - } - } -} diff --git a/Packages/src/Runtime/Internal/ProjectSettings/PreloadedProjectSettings.cs b/Packages/src/Runtime/Internal/ProjectSettings/PreloadedProjectSettings.cs index 90d98a09..1f24b872 100644 --- a/Packages/src/Runtime/Internal/ProjectSettings/PreloadedProjectSettings.cs +++ b/Packages/src/Runtime/Internal/ProjectSettings/PreloadedProjectSettings.cs @@ -14,7 +14,7 @@ namespace Coffee.UIEffectInternal public abstract class PreloadedProjectSettings : ScriptableObject #if UNITY_EDITOR { - private class MyAllPostprocessor : AssetPostprocessor + private class Postprocessor : AssetPostprocessor { private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____) { @@ -47,6 +47,11 @@ private static void Initialize() { SetDefaultSettings(defaultSettings); } + + if (defaultSettings) + { + defaultSettings.OnInitialize(); + } } } @@ -110,6 +115,10 @@ protected static void SetDefaultSettings(PreloadedProjectSettings asset) protected virtual void OnCreateAsset() { } + + protected virtual void OnInitialize() + { + } } #else { diff --git a/Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs b/Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs new file mode 100644 index 00000000..38c19f89 --- /dev/null +++ b/Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs @@ -0,0 +1,410 @@ +using System.Collections.Generic; +using UnityEngine; +using System; +#if UNITY_EDITOR +using System.IO; +using Object = UnityEngine.Object; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditorInternal; +#endif + +namespace Coffee.UIEffectInternal +{ + [Serializable] + public class ShaderVariantRegistry + { + [Serializable] + internal class StringPair : IEquatable + { + public string key; + public string value; + + public bool Equals(StringPair other) + { + if (other == null) return false; + if (ReferenceEquals(this, other)) return true; + return key == other.key && value == other.value; + } + + public override bool Equals(object obj) + { + return obj is StringPair other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((key != null ? key.GetHashCode() : 0) * 397) ^ (value != null ? value.GetHashCode() : 0); + } + } + } + + private Dictionary _cachedOptionalShaders = new Dictionary(); + + [SerializeField] + private List m_OptionalShaders = new List(); + + [SerializeField] + internal ShaderVariantCollection m_Asset; + +#if UNITY_EDITOR + [SerializeField] + private bool m_ErrorOnUnregisteredVariant = false; + + [SerializeField] + private List m_UnregisteredVariants = new List(); +#endif + + public ShaderVariantCollection shaderVariantCollection => m_Asset; + + public Shader FindOptionalShader(Shader shader, + string requiredName, + string format, + string defaultOptionalShaderName) + { + if (!shader) return null; + + // Already cached. + var id = shader.GetInstanceID(); + if (_cachedOptionalShaders.TryGetValue(id, out var optionalShaderName)) + { + return Shader.Find(optionalShaderName); + } + + // Required shader. + if (shader.name.Contains(requiredName)) + { + _cachedOptionalShaders[id] = shader.name; + return shader; + } + + // Find optional shader. + Shader optionalShader; + var shaderName = shader.name; + foreach (var pair in m_OptionalShaders) + { + if (pair.key != shaderName) continue; + optionalShader = Shader.Find(pair.value); + if (optionalShader) + { + _cachedOptionalShaders[id] = pair.value; + return optionalShader; + } + } + + // Find optional shader by format. + optionalShaderName = string.Format(format, shader.name); + optionalShader = Shader.Find(optionalShaderName); + if (optionalShader) + { + _cachedOptionalShaders[id] = optionalShaderName; + return optionalShader; + } + + // Find default optional shader. + _cachedOptionalShaders[id] = defaultOptionalShaderName; + return Shader.Find(defaultOptionalShaderName); + } + +#if UNITY_EDITOR + private HashSet _logVariants = new HashSet(); + + public void InitializeIfNeeded(Object owner, string optionalName) + { + // Register optional shader names by shader comment. + if (!string.IsNullOrEmpty(optionalName)) + { + var optionalShaders = ShaderUtil.GetAllShaderInfo() + .Where(s => s.name.Contains(optionalName)) + .Select(s => (s.name, path: AssetDatabase.GetAssetPath(Shader.Find(s.name)))) + .Where(x => !string.IsNullOrEmpty(x.path)) + .SelectMany(x => + { + return File.ReadLines(x.path) + .Take(10) + .Where(line => line.Contains($"OptionalShader@{optionalName}")) + .Select(line => Regex.Match(line, @":\s*(.*)$")) + .Where(match => match.Success) + .Select(match => new StringPair() { key = match.Groups[1].Value, value = x.name }); + }) + .Where(pair => m_OptionalShaders.All(x => x.key != pair.key)) + .ToArray(); + if (0 < optionalShaders.Length) + { + m_OptionalShaders.AddRange(optionalShaders); + EditorUtility.SetDirty(owner); + } + } + + if (!m_Asset && AssetDatabase.IsMainAsset(owner)) + { + // Find ShaderVariantCollection in owner. + var path = AssetDatabase.GetAssetPath(owner); + var collection = AssetDatabase.LoadAssetAtPath(path); + if (collection) + { + m_Asset = collection; + } + // Create new ShaderVariantCollection. + else + { + m_Asset = new ShaderVariantCollection() { name = "ShaderVariants" }; + AssetDatabase.AddObjectToAsset(m_Asset, owner); + } + + EditorUtility.SetDirty(owner); + AssetDatabase.SaveAssets(); + } + } + + internal void RegisterVariant(Material material, string path) + { + if (!material || !material.shader || !m_Asset) return; + + var shaderName = material.shader.name; + var validKeywords = material.shaderKeywords + .Where(x => !Regex.IsMatch(x, "(_EDITOR|EDITOR_)")) + .ToArray(); + var keywords = string.Join(" ", validKeywords); + var variant = new ShaderVariantCollection.ShaderVariant + { + shader = material.shader, + keywords = validKeywords + }; + + // Already registered. + var pair = new StringPair() { key = shaderName, value = keywords }; + if (m_Asset.Contains(variant)) + { + m_UnregisteredVariants.Remove(pair); + return; + } + + // Error when unregistered variant. + if (m_ErrorOnUnregisteredVariant) + { + if (!m_UnregisteredVariants.Contains(pair)) + { + m_UnregisteredVariants.Add(pair); + } + + if (_logVariants.Add(pair)) + { + keywords = string.IsNullOrEmpty(keywords) ? "no keywords" : keywords; + Debug.LogError($"Shader variant '{shaderName} <{keywords}>' is not registered.\n" + + $"Register it in 'ProjectSettings > {path}' to use it in player.", m_Asset); + } + + return; + } + + m_Asset.Add(variant); + m_UnregisteredVariants.Remove(pair); + } +#endif + } + +#if UNITY_EDITOR + internal class ShaderVariantRegistryEditor + { + private static readonly MethodInfo s_MiDrawShaderEntry = + Type.GetType("UnityEditor.ShaderVariantCollectionInspector, UnityEditor") + ?.GetMethod("DrawShaderEntry", BindingFlags.NonPublic | BindingFlags.Instance); + + private readonly SerializedProperty _errorOnUnregisteredVariant; + private readonly SerializedProperty _asset; + private readonly ReorderableList _rlOptionalShaders; + private readonly ReorderableList _rlUnregisteredVariants; + private Editor _editor; + private bool _expand; + + public ShaderVariantRegistryEditor(SerializedProperty property, string optionName) + { + var so = property.serializedObject; + var optionalShaders = property.FindPropertyRelative("m_OptionalShaders"); + var unregisteredVariants = property.FindPropertyRelative("m_UnregisteredVariants"); + _errorOnUnregisteredVariant = property.FindPropertyRelative("m_ErrorOnUnregisteredVariant"); + _asset = property.FindPropertyRelative("m_Asset"); + + _rlOptionalShaders = new ReorderableList(so, optionalShaders, false, true, true, true); + _rlOptionalShaders.drawHeaderCallback = rect => + { + var rLabel = new Rect(rect.x, rect.y, rect.width - 80, rect.height); + EditorGUI.LabelField(rLabel, + EditorGUIUtility.TrTextContent($"Optional Shaders {optionName}", + "Specify optional shaders explicitly.")); + + var rButton = new Rect(rect.x + rect.width - 80, rect.y, 80, rect.height - 4); + if (GUI.Button(rButton, "Clear All", EditorStyles.miniButton)) + { + optionalShaders.ClearArray(); + } + }; + _rlOptionalShaders.elementHeight = EditorGUIUtility.singleLineHeight * 2 + 4; + _rlOptionalShaders.drawElementCallback = (r, index, isActive, isFocused) => + { + if (optionalShaders.arraySize <= index) return; + + var element = optionalShaders.GetArrayElementAtIndex(index); + if (element == null) return; + + var key = element.FindPropertyRelative("key"); + var value = element.FindPropertyRelative("value"); + var h = EditorGUIUtility.singleLineHeight; + var rKey = new Rect(r.x, r.y + 2, r.width, h); + if (GUI.Button(rKey, key.stringValue, EditorStyles.popup)) + { + ShowShaderDropdown(key, optionName, false); + } + + var rArrow = new Rect(r.x, r.y + h + 4, 20, h); + EditorGUI.LabelField(rArrow, "->"); + + var rValue = new Rect(r.x + 20, r.y + h + 4, r.width - 20, h); + if (GUI.Button(rValue, value.stringValue, EditorStyles.popup)) + { + ShowShaderDropdown(value, optionName, true); + } + }; + + _rlUnregisteredVariants = new ReorderableList(so, unregisteredVariants, false, true, false, true); + _rlUnregisteredVariants.drawHeaderCallback = rect => + { + var rWarning = new Rect(rect.x, rect.y, 20, rect.height); + var icon = EditorGUIUtility.TrIconContent("warning", + "These variants are not registered.\nRegister them to use in player."); + EditorGUI.LabelField(rWarning, icon); + + var rLabel = new Rect(rect.x + 20, rect.y, 200, rect.height); + EditorGUI.LabelField(rLabel, "Unregistered Shader Variants"); + + var rButton = new Rect(rect.x + rect.width - 80, rect.y, 80, rect.height - 4); + if (GUI.Button(rButton, "Clear All", EditorStyles.miniButton)) + { + unregisteredVariants.ClearArray(); + } + }; + _rlUnregisteredVariants.elementHeight = EditorGUIUtility.singleLineHeight * 2 + 4; + _rlUnregisteredVariants.drawElementCallback = (r, index, isActive, isFocused) => + { + if (unregisteredVariants.arraySize <= index) return; + + var element = unregisteredVariants.GetArrayElementAtIndex(index); + if (element == null) return; + + var key = element.FindPropertyRelative("key"); + var value = element.FindPropertyRelative("value"); + + var h = EditorGUIUtility.singleLineHeight; + var rKey = new Rect(r.x, r.y + 2, r.width, h); + EditorGUI.LabelField(rKey, key.stringValue, EditorStyles.popup); + + var rValue = new Rect(r.x + 20, r.y + h + 5, r.width - 40, 14); + var keywords = string.IsNullOrEmpty(value.stringValue) ? "" : value.stringValue; + EditorGUI.TextField(rValue, GUIContent.none, keywords, "LODRenderersText"); + + var rButton = new Rect(r.x + r.width - 20, r.y + h + 4, 20, h); + if (GUI.Button(rButton, EditorGUIUtility.IconContent("icons/toolbar plus.png"), "iconbutton")) + { + var collection = _asset.objectReferenceValue as ShaderVariantCollection; + AddVariant(collection, key.stringValue, value.stringValue); + unregisteredVariants.DeleteArrayElementAtIndex(index); + } + }; + } + + public void Draw() + { + _rlOptionalShaders.DoLayoutList(); + _expand = DrawRegisteredShaderVariants(_expand, _asset, ref _editor); + if (0 < _rlUnregisteredVariants.serializedProperty.arraySize) + { + EditorGUILayout.Space(4); + _rlUnregisteredVariants.DoLayoutList(); + EditorGUILayout.Space(-20); + } + + var labelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 180; + EditorGUILayout.PropertyField(_errorOnUnregisteredVariant); + EditorGUIUtility.labelWidth = labelWidth; + } + + private static void AddVariant(ShaderVariantCollection collection, string shaderName, string keywords) + { + if (collection == null) return; + + var shader = Shader.Find(shaderName); + if (!shader) return; + + collection.Add(new ShaderVariantCollection.ShaderVariant + { + shader = shader, + keywords = keywords.Split(' ') + }); + EditorUtility.SetDirty(collection); + } + + private static bool DrawRegisteredShaderVariants(bool expand, SerializedProperty property, ref Editor editor) + { + var collection = property.objectReferenceValue as ShaderVariantCollection; + if (collection == null) return expand; + + EditorGUILayout.Space(); + var r = EditorGUILayout.GetControlRect(false, 20); + var rBg = new Rect(r.x - 3, r.y, r.width + 6, r.height); + EditorGUI.LabelField(rBg, GUIContent.none, "RL Header"); + + var rLabel = new Rect(r.x + 5, r.y, 200, r.height); + expand = EditorGUI.Foldout(rLabel, expand, "Registered Shader Variants"); + + var rButton = new Rect(r.x + r.width - 82, r.y + 1, 80, r.height - 4); + if (GUI.Button(rButton, "Clear All", EditorStyles.miniButton)) + { + collection.Clear(); + } + + if (expand) + { + EditorGUILayout.BeginVertical("RL Background"); + Editor.CreateCachedEditor(collection, null, ref editor); + editor.serializedObject.Update(); + var shaders = editor.serializedObject.FindProperty("m_Shaders"); + for (var i = 0; i < shaders.arraySize; i++) + { + s_MiDrawShaderEntry.Invoke(editor, new object[] { i }); + } + + EditorGUILayout.EndVertical(); + editor.serializedObject.ApplyModifiedProperties(); + } + + return expand; + } + + private static void ShowShaderDropdown(SerializedProperty property, string option, bool required) + { + var menu = new GenericMenu(); + var current = property.stringValue; + var allShaderNames = ShaderUtil.GetAllShaderInfo() + .Where(s => !required || s.name.Contains(option)) + .Select(s => s.name); + + foreach (var shaderName in allShaderNames) + { + menu.AddItem(new GUIContent(shaderName), shaderName == current, () => + { + property.stringValue = shaderName; + property.serializedObject.ApplyModifiedProperties(); + }); + } + + menu.ShowAsContext(); + } + } +#endif +} diff --git a/Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs.meta b/Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs.meta similarity index 83% rename from Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs.meta rename to Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs.meta index 9a18c247..dcd824b3 100644 --- a/Packages/src/Runtime/Internal/Extensions/TransformExtensions.cs.meta +++ b/Packages/src/Runtime/Internal/Utilities/ShaderVariantRegistry.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f901d0537c0db429e808496022199910 +guid: f5bfa8bba9d0847069c9091cfa8dfb84 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs b/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs new file mode 100644 index 00000000..ada24316 --- /dev/null +++ b/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs @@ -0,0 +1,82 @@ +using UnityEngine; + +namespace Coffee.UIEffectInternal +{ + public enum TransformSensitivity + { + Low, + Medium, + High + } + + /// + /// Extension methods for Transform class. + /// + internal static class TransformExtensionsForTransformSensitivity + { + private const float k_DefaultEpsilon = 1f / (2 ^ 8); + + /// + /// Check if a transform has changed. + /// + public static bool HasChanged(this Transform self, ref Matrix4x4 prev, TransformSensitivity sensitivity) + { + return self.HasChanged_Internal(null, ref prev, Convert(sensitivity)); + } + + /// + /// Check if a transform has changed. + /// + public static bool HasChanged(this Transform self, Transform baseTransform, ref Matrix4x4 prev, + TransformSensitivity sensitivity) + { + return self.HasChanged_Internal(baseTransform, ref prev, Convert(sensitivity)); + } + + private static float Convert(TransformSensitivity self) + { + switch (self) + { + case TransformSensitivity.Low: return 1f / (1 << 2); + case TransformSensitivity.Medium: return 1f / (1 << 5); + case TransformSensitivity.High: return 1f / (1 << 12); + default: return 1f / (1 << (int)self); + } + } + + private static bool HasChanged_Internal(this Transform self, Transform baseTransform, ref Matrix4x4 prev, + float epsilon) + { + if (!self) return false; + + var hash = baseTransform ? baseTransform.GetHashCode() : 0; + if (FrameCache.TryGet(self, nameof(HasChanged), hash, out bool result)) return result; + + var matrix = baseTransform + ? baseTransform.worldToLocalMatrix * self.localToWorldMatrix + : self.localToWorldMatrix; + var current = matrix * Matrix4x4.Scale(Vector3.one * 10000); + result = !Approximately(current, prev, epsilon); + FrameCache.Set(self, nameof(HasChanged), hash, result); + if (result) + { + prev = current; + } + + return result; + } + + private static bool Approximately(Matrix4x4 self, Matrix4x4 other, float epsilon = k_DefaultEpsilon) + { + for (var i = 0; i < 16; i++) + { + if (epsilon < Mathf.Abs(self[i] - other[i])) + { + return false; + } + } + + return true; + } + } +} diff --git a/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs.meta b/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs.meta new file mode 100644 index 00000000..1b55b604 --- /dev/null +++ b/Packages/src/Runtime/Internal/Utilities/TransformSensitivity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fd2b201d1fc94556b5bc7617788cd0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Runtime/Internal/Utilities/UIExtraCallbacks.cs b/Packages/src/Runtime/Internal/Utilities/UIExtraCallbacks.cs index 2a3f1562..b09e3b96 100755 --- a/Packages/src/Runtime/Internal/Utilities/UIExtraCallbacks.cs +++ b/Packages/src/Runtime/Internal/Utilities/UIExtraCallbacks.cs @@ -53,7 +53,7 @@ public static event Action onAfterCanvasRebuild /// /// Event that occurs when the screen size changes. /// - public static event Action onScreenSizeChangedAction + public static event Action onScreenSizeChanged { add => s_OnScreenSizeChangedAction.Add(value); remove => s_OnScreenSizeChangedAction.Remove(value); diff --git a/Packages/src/Runtime/UIEffectBase.cs b/Packages/src/Runtime/UIEffectBase.cs index f5ef81d9..6d41ce50 100644 --- a/Packages/src/Runtime/UIEffectBase.cs +++ b/Packages/src/Runtime/UIEffectBase.cs @@ -22,7 +22,6 @@ public abstract class UIEffectBase : UIBehaviour, IMeshModifier, IMaterialModifi { private static readonly VertexHelper s_VertexHelper = new VertexHelper(); private static Mesh s_Mesh; - private static readonly Dictionary s_ShaderNameCache = new Dictionary(); private static readonly ObjectPool s_ContextPool = new ObjectPool(() => new UIEffectContext(), x => true, x => x.Reset()); @@ -109,7 +108,10 @@ public virtual Material GetModifiedMaterial(Material baseMaterial) Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material"); MaterialRepository.Get(hash, ref _material, x => new Material(x) { - shader = FindShader(x), + shader = UIEffectProjectSettings.shaderRegistry.FindOptionalShader(x.shader, + "(UIEffect)", + "Hidden/{0} (UIEffect)", + "Hidden/UI/Default (UIEffect)"), hideFlags = HideFlags.HideAndDontSave }, baseMaterial); Profiler.EndSample(); @@ -120,33 +122,6 @@ public virtual Material GetModifiedMaterial(Material baseMaterial) return _material; } - - private static Shader FindShader(Material material) - { - var shader = material.shader; - var hash = shader.GetInstanceID(); - if (!s_ShaderNameCache.TryGetValue(hash, out var shaderName)) - { - shaderName = shader.name; - if (!shaderName.Contains("(UIEffect)")) - { - shaderName = $"Hidden/{shaderName} (UIEffect)"; - } - - var uiEffectShader = Shader.Find(shaderName); - if (!uiEffectShader) - { - shaderName = "Hidden/UI/Default (UIEffect)"; - uiEffectShader = Shader.Find(shaderName); - } - - s_ShaderNameCache[hash] = shaderName; - return uiEffectShader; - } - - return Shader.Find(shaderName); - } - #if UNITY_EDITOR protected override void OnValidate() { @@ -229,7 +204,7 @@ public virtual void ApplyContextToMaterial() context.ApplyToMaterial(_material); #if UNITY_EDITOR - UIEffectProjectSettings.RegisterVariant(_material); + UIEffectProjectSettings.shaderRegistry.RegisterVariant(_material, "UI > UIEffect"); if (!EditorApplication.isPlaying) { EditorApplication.QueuePlayerLoopUpdate(); @@ -239,13 +214,12 @@ public virtual void ApplyContextToMaterial() #if TMP_ENABLE #if UNITY_EDITOR - private class MyAllPostprocessor : AssetPostprocessor + private class Postprocessor : AssetPostprocessor { private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____) { if (Application.isBatchMode || BuildPipeline.isBuildingPlayer) return; - s_ShaderNameCache.Clear(); foreach (var effect in Misc.FindObjectsOfType() .Concat(Misc.GetAllComponentsInPrefabStage())) { diff --git a/Packages/src/Runtime/UIEffectProjectSettings.cs b/Packages/src/Runtime/UIEffectProjectSettings.cs index 8682e2bb..b9eabf7a 100644 --- a/Packages/src/Runtime/UIEffectProjectSettings.cs +++ b/Packages/src/Runtime/UIEffectProjectSettings.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text.RegularExpressions; using Coffee.UIEffectInternal; -using Coffee.UIEffects; using UnityEditor; using UnityEngine; @@ -11,19 +10,6 @@ namespace Coffee.UIEffects { public class UIEffectProjectSettings : PreloadedProjectSettings { - public enum FallbackVariantBehaviour - { - RegisterShaderVariant, - LogError - } - - public enum TransformSensitivity - { - Low, - Medium, - High - } - [Tooltip( "The sensitivity of the transformation when `Use Target Transform` is enabled in the `UIEffectReplica` component.")] [Header("Setting")] @@ -33,16 +19,17 @@ public enum TransformSensitivity [SerializeField] internal List m_RuntimePresets = new List(); + [HideInInspector] [SerializeField] - [Header("Editor")] - internal FallbackVariantBehaviour m_FallbackVariantBehaviour = FallbackVariantBehaviour.RegisterShaderVariant; + internal ShaderVariantCollection m_ShaderVariantCollection; + [HideInInspector] [SerializeField] - internal List m_UnregisteredShaderVariants = new List(); + private ShaderVariantRegistry m_ShaderVariantRegistry = new ShaderVariantRegistry(); - [SerializeField] - [Header("Shader")] - internal ShaderVariantCollection m_ShaderVariantCollection; + public static ShaderVariantRegistry shaderRegistry => instance.m_ShaderVariantRegistry; + + public static ShaderVariantCollection shaderVariantCollection => shaderRegistry.shaderVariantCollection; public static TransformSensitivity transformSensitivity { @@ -50,22 +37,6 @@ public static TransformSensitivity transformSensitivity set => instance.m_TransformSensitivity = value; } - public static ShaderVariantCollection shaderVariantCollection => instance.m_ShaderVariantCollection; - - public static float sensitivity - { - get - { - switch (instance.m_TransformSensitivity) - { - case TransformSensitivity.Low: return 1f / (1 << 2); - case TransformSensitivity.Medium: return 1f / (1 << 5); - case TransformSensitivity.High: return 1f / (1 << 12); - default: return 1f / (1 << (int)instance.m_TransformSensitivity); - } - } - } - public static void RegisterRuntimePreset(UIEffect effect) { // Already registered. @@ -98,13 +69,17 @@ public static UIEffect LoadRuntimePreset(string presetName) protected override void OnCreateAsset() { - m_ShaderVariantCollection = new ShaderVariantCollection() - { - name = "UIEffectShaderVariants" - }; - AssetDatabase.AddObjectToAsset(m_ShaderVariantCollection, this); - EditorUtility.SetDirty(this); - AssetDatabase.SaveAssets(); + m_ShaderVariantRegistry.InitializeIfNeeded(this, "(UIEffect)"); + } + + protected override void OnInitialize() + { + m_ShaderVariantRegistry.InitializeIfNeeded(this, "(UIEffect)"); + } + + private void Reset() + { + m_ShaderVariantRegistry.InitializeIfNeeded(this, "(UIEffect)"); } internal static UIEffect[] LoadEditorPresets() @@ -153,60 +128,6 @@ internal static string GetPresetPath(UIEffect preset) var m = Regex.Match(assetPath, k_PresetPathPattern); return m.Success ? m.Groups[1].Value : Path.GetFileNameWithoutExtension(assetPath); } - - internal static void ClearUnregisteredShaderVariants() - { - instance.m_UnregisteredShaderVariants.Clear(); - } - - internal static void RegisterVariant(string variant) - { - var values = variant.Split(';'); - instance.m_ShaderVariantCollection.Add(new ShaderVariantCollection.ShaderVariant() - { - shader = Shader.Find(values[0]), - keywords = values[1].Split('|') - }); - instance.m_UnregisteredShaderVariants.Remove(variant); - } - - internal static void RegisterVariant(ShaderVariantCollection.ShaderVariant variant) - { - instance.m_ShaderVariantCollection.Add(variant); - } - - internal static void RegisterVariant(Material material) - { - if (!material || !material.shader || !instance.m_ShaderVariantCollection) return; - - var variant = new ShaderVariantCollection.ShaderVariant - { - shader = material.shader, - keywords = material.shaderKeywords - }; - - // Already registered. - if (instance.m_ShaderVariantCollection.Contains(variant)) return; - - switch (instance.m_FallbackVariantBehaviour) - { - case FallbackVariantBehaviour.RegisterShaderVariant: - RegisterVariant(variant); - break; - case FallbackVariantBehaviour.LogError: - var shaderName = variant.shader.name; - var keywords = string.Join("|", variant.keywords); - var v = $"{shaderName};{keywords}"; - if (!instance.m_UnregisteredShaderVariants.Contains(v)) - { - instance.m_UnregisteredShaderVariants.Add(v); - } - - Debug.LogError($"{shaderName} with keywords <{keywords}> is not registered.\n" + - "Please register it in 'Project Settings > UI > UIEffect > Shader'."); - return; - } - } #endif } } diff --git a/Packages/src/Runtime/UIEffectReplica.cs b/Packages/src/Runtime/UIEffectReplica.cs index e7759fe5..6bba3700 100644 --- a/Packages/src/Runtime/UIEffectReplica.cs +++ b/Packages/src/Runtime/UIEffectReplica.cs @@ -105,7 +105,7 @@ private bool CheckTransform() return useTargetTransform && target && transform.HasChanged(target.transform, ref _prevTransformHash, - UIEffectProjectSettings.sensitivity); + UIEffectProjectSettings.transformSensitivity); } private void SetVerticesDirtyIfTransformChanged()