2.5 性能:刷新策略与无效化

Slate 性能的两个指标

  1. Tick 时间:每帧每个控件 OnTick 的总耗时
  2. 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 内建虚拟化(只生成可见行),但有两个前提:

  1. ItemHeight 要设置且准确(不准确会导致滚动跳跃/渲染错位)
  2. 数据源指针稳定,变化时 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 的性能问题基本绝迹。