Files
2026-09-10 17:29:24 +08:00

75 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UIEffect 降 DC 改动总结
> 提交:`fa5f0cdb 降低DC的操作`(2026-08-26)
> 涉及目录:`unity/my_libs/unityLib/UIEffect-main/Packages/src/Runtime/`
## 一、背景与目标
UI_Effect 插件原本每个 `UIEffect` 组件都会生成**独立的材质实例**(缓存 key 使用 `effectId`,即组件的 `GetInstanceID()`,每个实例唯一),导致:
- 相同配置的特效无法共享材质 → 无法参与动态合批 → **Draw Call 偏高**;
- 批量刷 UI 时材质创建开销大,伴随帧率波动。
目标:让**相同配置的 UIEffect 共享同一材质**,从而参与合批,降低 DC。
## 二、方案选型
| 方案 | 说明 | 结论 |
| --- | --- | --- |
| UIEffectReplica + Preset | 业务代码逐个改为副本组件,指向共享 preset | 需要改动大量业务文件、创建 preset 资产,侵入性大 |
| **configId 配置指纹(最终采用)** | 在插件内部按"配置内容哈希"共享材质 | **业务代码零改动**,同配置自动共享合批 |
## 三、具体改动
### 1. `UIEffectContext.cs`(新增约 121 行)
核心:新增 `configId` 属性——特效配置的"稳定指纹"。
- **反射字段缓存**:`s_ConfigFields` 为 `static readonly FieldInfo[]`,`GetFields` 只在类型加载时执行一次,运行时无反射类型发现开销,避免批量刷新时的 GC 尖峰(这是此前"帧率不稳"问题的根因与修复点)。
- **内容哈希 `HashValue`**:
- `null` → 0;
- `UnityEngine.Object` → `GetInstanceID()`(引用同一资产即同哈希);
- `Gradient` → `GradientHash` 按 colorKeys / alphaKeys 逐 key 做内容哈希(不同实例但内容相同的渐变得到相同指纹);
- 基元/枚举 → `GetHashCode()`;嵌套值类型(Color、Vector、MinMax01 等)→ `HashFields` 逐字段展开(`s_FieldCache` 按类型缓存)。
- **雪崩混淆**:FNV-1a 混合后追加 MurmurHash3 终混,让相近配置散列开,降低哈希碰撞概率。
### 2. `UIEffectBase.cs`(`GetModifiedMaterial`)
材质缓存 key 由 `effectId` 改为配置指纹:
```csharp
var configId = context.configId;
var rootId = context.useViewMatrix && transitionRoot ? (uint)transitionRoot.GetInstanceID() : 0u;
var hash = new Hash128((uint)baseMaterial.GetInstanceID(), configId, samplingScaleId, rootId);
```
要点:
- `rootId` **仅在特效需要视图矩阵时**(transition / gradation / detail / shiny)才参与哈希,普通灰度(ToneFilter)等不受 root 影响,可共享范围更大;
- `Hash128(baseMaterial, configId, samplingScaleId, rootId)` 相同 → 命中 `MaterialRepository` 中同一材质实例 → 参与动态合批。
### 3. `ShaderVariantRegistry.cs`(Profiler 采样修复)
`RegisterVariant` / `GetVariant` 的**所有提前 return 分支**均补上 `Profiler.EndSample()`,保证 `BeginSample`/`EndSample` 严格配对,修复控制台的:
```
Non-matching Profiler.EndSample (Every EndSample call must have a preceding BeginSample call...)
```
## 四、业务侧影响
- **业务文件零改动**:`EquipBuffItem`、`MapBlockItem`、`PetItem`、`V_RechargeItem`、`FightLinkHeroItem`、`AvatarItem` 等继续正常使用 `UIEffect` 控制灰度,无需迁移到 `UIEffectReplica`。
- 同屏所有**同配置**灰度特效(如大量 `ToneFilter.Grayscale` 的图标)自动共享一个材质并合批。
## 五、效果
- N 个相同配置的 UIEffect:N 个材质实例 → **1 个共享材质**,可被 UGUI 动态合批;
- DC 显著下降(受益程度取决于面板内同配置特效数量)。
## 六、注意事项与局限
1. **配置不同的特效无法互相合批**——每种不同配置组合仍各占一个材质/DC,属预期行为;
2. `configId` 在画布重建时计算,字段遍历存在少量装箱开销,已通过静态缓存降到最低(普通 UI 不会每帧触发);
3. 新增 `UIEffectContext` 公共字段会自动纳入指纹(遍历 public instance 字段),**无需手动维护字段清单**;
4. 验证方式:打开 Frame Debugger / Profiler,确认同配置 UI 元素合并为同一批次;确认控制台不再出现 `Non-matching Profiler.EndSample`。