diff --git a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs index c3ceb2fd..210257d0 100644 --- a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs +++ b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectBase.cs @@ -196,8 +196,11 @@ namespace Coffee.UIEffects Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial"); var samplingScaleId = (uint)(Mathf.InverseLerp(0.01f, 100, actualSamplingScale) * uint.MaxValue); - var rootId = (uint)(transitionRoot != null ? transitionRoot.GetHashCode() : 0); - var hash = new Hash128((uint)baseMaterial.GetHashCode(), effectId, samplingScaleId, rootId); + // 使用特效配置哈希作为材质共享 key,相同配置的特效共享同一材质,从而可以合批。 + // root 仅在特效需要视图矩阵(transition/gradation/detail/shiny)时才影响材质。 + var configId = context.configId; + var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u; + var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId); if (!MaterialRepository.Valid(hash, _material)) { Profiler.BeginSample("(UIE)[UIEffect] GetModifiedMaterial > Get or create material"); diff --git a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs index c315f4cb..48b6ae4b 100644 --- a/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs +++ b/unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/UIEffectContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Coffee.UIEffectInternal; using UnityEngine; using UnityEngine.Profiling; @@ -263,6 +264,126 @@ namespace Coffee.UIEffects || m_DetailFilter != DetailFilter.None || m_EdgeMode == EdgeMode.Shiny; + /// + /// 特效配置的稳定指纹。自动遍历所有序列化配置字段生成,因此插件新增属性时无需手动维护字段清单。 + /// 相同配置会得到相同指纹,从而可以共享同一个材质并参与合批。 + /// + public uint configId + { + get + { + unchecked + { + var h = (uint)2166136261; + var fields = s_ConfigFields; + for (var i = 0; i < fields.Length; i++) + { + var f = fields[i]; + h = Mix(h, (uint)f.Name.GetHashCode()); + h = Mix(h, HashValue(f.GetValue(this))); + } + + // Avalanche 混淆(MurmurHash3 终混),让相近配置散列到不同指纹。 + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; + } + } + } + + private static uint Mix(uint h, uint v) + { + h ^= v; + h *= 16777619; + return h; + } + + /// + /// 对任意配置值做内容哈希。值类型会逐字段展开,嵌套结构体(Color、Vector2/3/4、MinMax01 等) + /// 无需单独写类型处理即可贡献全部数据。只有 UnityEngine.Object 和 Gradient 需要显式处理, + /// 因为它们的默认哈希基于引用而非内容。 + /// + private static uint HashValue(object value) + { + if (value == null) return 0u; + + if (value is UnityEngine.Object obj) + { + return obj ? (uint)obj.GetInstanceID() : 0u; + } + + if (value is Gradient gradient) + { + return GradientHash(gradient); + } + + var type = value.GetType(); + if (type.IsPrimitive || type.IsEnum) + { + return unchecked((uint)value.GetHashCode()); + } + + if (type.IsValueType) + { + return HashFields(value, type); + } + + return unchecked((uint)value.GetHashCode()); + } + + private static uint HashFields(object boxed, Type type) + { + unchecked + { + if (!s_FieldCache.TryGetValue(type, out var fields)) + { + fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + s_FieldCache[type] = fields; + } + + var h = (uint)2166136261; + for (var i = 0; i < fields.Length; i++) + { + h = Mix(h, HashValue(fields[i].GetValue(boxed))); + } + + return h; + } + } + + private static uint GradientHash(Gradient gradient) + { + unchecked + { + var h = (uint)2166136261; + + var colorKeys = gradient.colorKeys; + for (var i = 0; i < colorKeys.Length; i++) + { + h = Mix(h, HashValue(colorKeys[i].color)); + h = Mix(h, HashValue(colorKeys[i].time)); + } + + var alphaKeys = gradient.alphaKeys; + for (var i = 0; i < alphaKeys.Length; i++) + { + h = Mix(h, HashValue(alphaKeys[i].alpha)); + h = Mix(h, HashValue(alphaKeys[i].time)); + } + + return h; + } + } + + private static readonly FieldInfo[] s_ConfigFields = typeof(UIEffectContext) + .GetFields(BindingFlags.Public | BindingFlags.Instance); + + private static readonly Dictionary s_FieldCache = + new Dictionary(); + private Texture2D _gradationRampTex; private Texture2D _transitionRampTex; private bool _isGradientDirty = true;