2.5 性能:刷新策略与无效化
Slate 性能的两个指标
- Tick 时间:每帧每个控件 OnTick 的总耗时
- Paint 时间:重绘成本
Slate 性能问题的典型症状:编辑器整体卡顿、工具面板滚动掉帧、打开了工具后编辑器变慢。排查从「Slate Insights」(https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/slate-insights-in-unreal-engin-e81c)开始,能看到每个控件的 Tick/Paint 耗时排行。
无效化(Invalidation):核心机制
Slate 的重绘不是每帧全量。控件默认是「缓存绘制结果,失效后才重绘」。失效来源:
- 布局变化(尺寸、Padding 变)
- 属性绑定的值变了(绑定函数每次调,但返回值不同才真的失效)
- 显式调
Invalidate(EInvalidateWidgetReason::Paint)等
性能杀手:绑定函数返回的值每次都「看起来不同」——比如 FText::AsNumber(GetTime()) 每帧返回不同字符串,整个控件树每帧失效重绘。修正:只在数据真正变化时返回新值。
官方对此的完整设计叫「UI 无效化」(https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/invalidation-in-slate-and-umg-52e5),核心思想:让控件在依赖未变时不进入重绘队列。
ListView 的大数据集
SListView 内建虚拟化(只生成可见行),但有两个前提:
ItemHeight要设置且准确(不准确会导致滚动跳跃/渲染错位)- 数据源指针稳定,变化时
RequestListRefresh()而不是重建 ListView
错误示例(卡顿现场):
// 每 OnGenerateRow 都 new 一个复杂 Widget
OnGenerateRow_Lambda([](auto Item, auto Table){
return SNew(STableRow)[
SNew(SVerticalBox) // 每行 20 个子控件,1 万数据滚一下生成几百次
+ Slot()[ ... ] × 20
];
});
修正:
- 行控件尽量扁平(一个 HorizontalBox + 2~3 个 TextBlock)
- 复杂行内容用 SObjectWidget 缓存或自绘
- 真正复杂的行(如缩略图)做显式缓存池
Tick 的纪律
SNew(SCompoundWidget)
+ Slot()[ SNew(SImage).Image_Lambda([](){ return GetCurrentTexture(); }) ]
// ↑ 这是个隐式 Tick:Image_Lambda 每帧调用
常见问题:
| 坏味道 | 修正 |
|---|---|
| 绑定函数里扫资产/查 AssetRegistry | 事件触发时缓存结果,绑定读缓存 |
| 每帧 RequestListRefresh | 改为数据变化时才 Refresh |
| Tick 里做字符串拼接/FString 操作 | 缓存 |
| 用 Tick 做动画 | 用 cur效果/动画曲线或事件驱动 |
「休眠计时器」
工具面板不活跃时(窗口不在前台、用户没操作),Slate 提供了「休眠计时器」机制(https://www.wanxiangyundang.top/read/unreal-engine-5-8-docs/slate-ui-sleeping-and-active-t-29d7)——降低后台面板的更新频率。对有轮询需求的工具(如监视文件夹变化),用 RegisterActiveTimer / RegisterSleeptimer 而不是死 Tick。
折叠:减少可见控件
最直接的加速:不可见的控件不渲染。折叠面板(SExpanderArea)、标签页(SWidgetSwitcher 而不是 SOverlay 堆叠)能真正省 Paint 成本。注意 SWidgetSwitcher 只有激活页参与绘制,而 SOverlay 全部参与——藏着的弹层要 Collapse 而不是盖起来。
一个真实优化案例
某资产检查工具打开后编辑器 FPS 从 120 掉到 45。Slate Insights 排查:详情面板的 40 个 TextBlock 每个绑了「资产依赖数」,绑定函数每次都调 AssetRegistry 查询(40×每帧×每秒 60 = 2400 次查询)。修正:选中项变化时批量查一次缓存起来,绑定读缓存 → FPS 恢复。
心得:Slate 性能问题的 90% 是「绑定函数太重」。开发早期就立个规矩:绑定函数里不允许出现 AssetRegistry、文件 IO、字符串格式化、任何循环——只读缓存字段。这条规矩执行下去,工具 UI 的性能问题基本绝迹。
