This commit is contained in:
ShallowT1Dream
2026-09-10 17:29:24 +08:00
parent 3071ec4b8f
commit 80aa4f0436
20 changed files with 32618 additions and 0 deletions
@@ -0,0 +1,175 @@
# DC 与帧率排查经验
> 从 T5 / T11 两个工程的实际排查里沉淀下来的方法论与踩坑记录。
> 配合 `01_UIEffect材质共享降DC/移植与验证手册.md` 使用:手册负责"怎么改",本文负责"改完怎么看、看不明白怎么办"。
> 全屏遮挡机制的专项排查(含团结引擎加密 guid 的坑)见 `02_全屏面板遮挡省DC/FullScreenCover_使用方法与避坑.md` 第八章。
---
## 一、Frame Debugger 看图技巧(最常用)
`Window → Analysis → Frame Debugger` → Enable。
### 1.1 纹理名怎么读
| 纹理名形态 | 含义 |
| --- | --- |
| `sactx-0-1024x1024-DXT5\|BC3-atlas-<hash>` | **SpriteAtlas 运行时图集**(`sactx` = Sprite Atlas Texture)。`<hash>` 标识具体哪个图集,两个批次 hash 不同 = 不同图集 = 不可能合批 |
| `Font Texture` / `xxx SDF` | TextMeshPro / 字体图集,跟普通 Image 天然不同纹理 |
| 具体贴图名(如 `ui_icon_gold`) | 散图,没进图集。**大量散图 = DC 大户**,应归并进 SpriteAtlas |
### 1.2 "预览是黑的"不等于浪费
Frame Debugger 右侧预览里 mesh 显示为黑色/深色,通常是 **mesh 的 UV 落在了图集的透明区域**——
透明像素的 RGB 通道常被填成黑色(alpha=0),预览只画 RGB 不乘 alpha,所以看起来是黑的。
判断方法:看这一批的 **顶点数 / 纹理名 / 所属 Canvas**,而不是看预览颜色。
真正确认"这一批画了什么"的办法:在事件树里选中该 Draw Mesh,Scene 视图会高亮对应元素。
> T5 实例:通行证面板的"黑批次"实际是 `sactx-...-atlas-bf6ec7a7` 图集里的一块透明区域,不是无效绘制。
### 1.3 别把 Event 编号当批次序号
Frame Debugger 左侧的 `Event #N` 是**整个帧的事件序号**(含相机 clear、ShadowMap、UI 之外的绘制),
不是"UI 第 N 批"。
> T5 实例:双相机结构下 `Event #6` 对应的其实是 UI 的**第 1 批**(偏移 +5)。
> 换一台机器、换一个场景结构,偏移量就变了。
正确做法:只统计 `Canvas.BuildBatch (<CanvasName>)` 节点**下面**的 `Draw Mesh` 子项数量,那才是 UI 批次数。
### 1.4 定位"谁在拆批次"的标准流程
1. 展开 `Canvas.BuildBatch`,数子项数量(= 该 Canvas 的批次数);
2. 逐个点子项,记下每项的 **Material + Texture**;
3. 相邻两项若 Material 相同但 Texture 不同 → **图集问题**(合图集);
4. 若 Material 不同 → 看是不是 UIEffect / Mask / 自定义材质导致(本补丁解决的就是 UIEffect 这一类);
5. 若 Material、Texture 都相同却还是两批 → 中间夹了别的元素打断了层级相邻性(调 hierarchy 顺序)。
---
## 二、"帧率不稳"的排查(先分清是真问题还是假象)
### 2.1 编辑器里的 FPS 数字乱跳 ≠ 真机卡
三个必然存在的干扰因素:
1. **Stats 面板是滚动平均**:本身就有延迟和抖动,瞬时值无意义;
2. **编辑器自身开销**:Scene 视图重绘、Inspector GUI repaint、资源导入、Play 模式的 EditorApplication update,都会吃掉帧时间,且**不均匀**;
3. **vSync 量化**:开了垂直同步后帧率只能在 60 / 30 / 20 这些档位跳,看起来像"忽高忽低",其实是量化台阶。
**结论**:判断帧率问题必须用 Profiler 看**帧时间**,不要用编辑器 Stats 的 FPS 数字,更不要靠肉眼感觉。
### 2.2 Profiler 三步法
`Window → Analysis → Profiler`,勾 CPU Usage,连真机(或编辑器 Play):
**第一步:看帧时间尖峰,不看平均值**
- 图表区找突然拉高的柱子(正常 60 帧 = 16.6ms,超过 33ms 就是明显卡顿);
- 区分"**偶发尖峰**"(GC、资源加载、实例化)和"**持续偏高**"(每帧都慢,通常是渲染或 Update 逻辑)。
**第二步:展开尖峰帧,找两类元凶**
- `GC.Alloc` / `GC.Collect`:内存分配导致的卡顿。看调用栈定位到具体函数;
- 耗时 Top 函数:常见是 `Canvas.SendWillRenderCanvases`(UI Rebuild)、`SkeletonGraphic.Update`(Spine)、`Physics`、`Animator.Update`。
**第三步:对号入座**
| Profiler 里看到的 | 通常原因 | 处理方向 |
| --- | --- | --- |
| `Canvas.SendWillRenderCanvases` 高 | UI Rebuild 频繁(Text 改字、SetActive 抖动、布局组件重算) | 减少每帧改 UI;避免 LayoutGroup 嵌套;文字更新加脏标记 |
| `Canvas.BuildBatch` 高 | 批次太多 / Canvas 太大 | 降 DC(本包主题);拆 SubCanvas 隔离动态区 |
| `GC.Alloc` 持续出现 | 每帧 new / 装箱 / 字符串拼接 / foreach 某些集合 | 缓存对象;用 struct 枚举;避免运行时 `$"..."` 拼路径 |
| `SkeletonGraphic` 相关高 | Spine UI 骨骼持续驱动 | 不可见时禁用组件;降低 update mode 频率 |
| `Shader.Parse` / `Material` 创建 | 材质实例被反复创建 | 本补丁正是解决 UIEffect 这一类 |
### 2.3 帧率设置本身的自查(排除"根本没设 60 帧")
```powershell
# 在目标工程里搜这几处,确认帧率策略
cd <目标工程>\unity
Select-String -Path (Get-ChildItem -Recurse -Filter *.cs -Exclude *Library*).FullName `
-Pattern "targetFrameRate|vSyncCount|OnApplicationFocus|OnApplicationPause" |
Select-Object Path, LineNumber, Line
```
T5 的实际情况(可作为参考范式):
- 启动即设:`GameInitWrapper.cs` → `AppUtils.Init(60, true)` → `Application.targetFrameRate = 60`;
- 真机切后台降帧:`Engine.cs` 的 `OnApplicationFocus`,在 `#if (UNITY_ANDROID || UNITY_IOS) && !UNITY_EDITOR` 内,
失焦 → 10 帧,回前台 → 60 帧。
> **重要**:这个"后台降 10 帧"经常被误读成"帧率不稳"。它只在真机切后台时生效,编辑器里不触发。
> 排查时先确认这类分支有没有被误命中(比如某些平台宏定义写错,导致编辑器也走进去了)。
### 2.4 对照实验:怀疑某个组件时,禁用它再测
判断"是不是这个组件在拖帧"的唯一可靠方法是**对照实验**:
1. Profiler 开着,记录当前帧时间基线;
2. 运行时把嫌疑组件 `enabled = false`(或在 Inspector 里取消勾选);
3. 看帧时间是否明显下降;
4. 恢复,确认帧时间回到基线(排除"只是这一帧刚好快了")。
> T5 实例:大厅 `UI_MainPanel` 挂了 `sk_hero` + `sk_pet` 两个 `SkeletonGraphic`(UI Spine),
> 是"持续负载"最大嫌疑。验证方式就是运行时禁用这两个组件看帧时间变化。
> **注意**:对照实验只做临时禁用,不要顺手删组件——删之前必须 grep 业务代码引用(见 §4)。
---
## 三、UI 降 DC 的通用手段清单(按性价比排序)
本包的 configId 补丁只解决"UIEffect 拆批次"这一项。完整的降 DC 手段:
| 手段 | 收益 | 成本 | 说明 |
| --- | --- | --- | --- |
| **UIEffect 共享材质(本包路线①)** | 高(同配置特效密集时) | 极低(改插件 2 文件,业务零改动) | 见 `01_UIEffect材质共享降DC/` |
| **全屏面板遮挡(本包路线②)** | 高(全屏页多、下层界面复杂时) | 低(挂组件,业务零改动) | 见 `02_全屏面板遮挡省DC/`;**有两个生命周期陷阱,必看文档第七章** |
| **关掉无用的 URP Feature(本包路线③)** | 中(T5 实测 SSAO 占 4 DC) | 极低(改一个开关) | 见 `03_渲染设置与资源清理/`;2D/UI 场景关 SSAO 无视觉损失 |
| **散图归并进 SpriteAtlas** | 高 | 中(需美术/UI 配合规划图集) | 一个面板的图尽量进同一个图集 |
| **统一 UIEffect 配置** | 中高 | 低(改 prefab 参数) | 补丁生效的前提是"配置相同",美术参数乱调会抵消收益 |
| **调整 hierarchy 顺序让同图集相邻** | 中 | 低 | 合批要求层级相邻,穿插即断批 |
| **减少 Mask / RectMask2D** | 中 | 中 | 每个 Mask 内部独立成批,跨 Mask 永不合批 |
| **拆 SubCanvas 隔离高频变化区** | 中(降 Rebuild 更明显) | 中 | 动静分离;但 SubCanvas 会各自建批,别拆过头 |
| **TMP 字体图集与 Image 分区** | 低中 | 低 | 文字与图片天然不同纹理,布局上让它们各自成组 |
| **控制同屏 Spine(SkeletonGraphic)数量** | 中 | 中 | 每个至少独占一批且打断周围 UI 合批;不可见时禁用 |
---
## 四、血泪教训(务必遵守)
1. **删任何 UI 组件前,先 grep 业务代码有没有引用它。**
T5 曾误判"某 UIEffect 没有视觉效果"就删了组件,结果业务代码 `GetComponent<UIEffect>()` 拿到 null,运行时 NRE。
```powershell
# 删组件前的强制动作:确认无引用
cd <目标工程>\unity\Assets
Select-String -Path (Get-ChildItem -Recurse -Filter *.cs).FullName `
-Pattern "UIEffect" | Select-Object Path, LineNumber, Line
```
2. **不要用肉眼/主观感受下性能结论。**"我感觉有点卡"必须先变成 Profiler 里的帧时间数字,再谈优化。
3. **改插件源码前先确认它不是 PackageCache 只读副本**,否则改完 Unity 一刷新就没了(详见手册 §1.1)。
4. **补丁一定要提交进 git 并写清楚 commit message**,插件升级后才知道要重打什么。
5. **优化改动要单独提交,别和无关改动混在一个 commit 里。**
T5 的 `c61b9012 优化DC` 就混进了 7 个 prefab 的 FullScreenCover 配置改动,导致后来做归档时必须手工拆分 diff。
---
## 五、附:UI DC 体检工具设计规格(源码已丢失,可据此重建)
T5 曾有一个自研 Editor 工具 `UIDCProbeTool.cs`(`Assets/Editor/` 下),删除时**未提交 git**,源码不可恢复。
如果新项目需要比 Frame Debugger 更高效的批量归因工具,按下面规格重建即可(约 800 行,一个下午的工作量):
**定位**:只读的 EditorWindow,不改场景、不改资源,专门回答"这个面板的 DC 被谁拆了"。
**两种模式**:
1. **真实批次模式**:Play 模式下遍历目标 Canvas,按 UGUI 的合批规则(材质 + 纹理 + 层级相邻 + 同 Canvas)模拟分批,输出批次列表;**批次 index 从 1 开始计数**(不是 0,避免与 Frame Debugger 的 Event 编号混淆)。
2. **材质分组模式**:把所有可绘制元素按 `材质.GetInstanceID() + 主纹理.GetInstanceID()` 归组,每组显示"元素数量 / 涉及对象路径 / 纹理名",直接暴露"哪种材质独占了多少批"。支持按纹理 hash 搜索(比如输入 Frame Debugger 里看到的 `bf6ec7a7` 反查是哪个图集)。
**输出字段(每批一行)**:批次 index、Canvas 路径、材质名、纹理名、元素数量、是否可合并到前一批、不可合并原因(图集不同 / 材质不同 / 层级被打断 / Mask 隔离)。
**界面**:顶部选 Canvas(下拉 + Ping 按钮定位)+ 模式切换 + 搜索框;列表分页(每页 100 条);支持导出 CSV(UTF8 BOM,Excel 直接打开不乱码);底部汇总行(总批次 / 可优化批次 / 预估可降到多少)。
**关键实现提示**:
- 全程走 `AssetDatabase` / 反射读 Canvas 内部批次数据时注意团结引擎(Tuanjie)对 guid 的加密,**不要依赖 guid 字符串**,用 `GetInstanceID()` 做 key;
- 只做只读分析,不 `SetActive`、不改材质、不 Undo 记录;
- 工具本身放 `Assets/Editor/`,用完可以整文件删除(记得连 `.meta` 一起删,否则 Unity 报孤立 meta 警告)。
> 如果只是想快速定位问题,**Unity 自带 Frame Debugger + 本文第一章的方法已经够用**,不必重建工具。