LWE 帧流水线性能排查
完整 Scene 能播放以后,LWE 的下一件事是查清楚为什么帧率仍然偏低。成功一帧的 render 加 swap 只有约 5–6 ms,但这个数字没有包含脚本和动画纹理准备,更没有解释两次成功呈现之间都在等什么。
这篇记录 10 月 13–15 日已合并的排查。文中的性能数字来自当时公开的验收记录,本文没有重新运行这些实验。这里约 15 FPS 的短窗口结果,与长运行验收约 27.76 swap/s 的统计使用不同测试输入和窗口,不能直接比较或拼成一条优化时间线。
先看两帧之间发生了什么
一帧需要脚本结果、当前音频输入、动画区域和文字。worker 准备期间,引擎可能已经收到 compositor 回调,也可能正在等待 FPS 截止时间。只对最终绘制与 swap 计时,就会把前面的空白留在视野之外。
原来的 Scene 路径还有一个额外代价:尝试呈现时发现 VM 或纹理 worker 没准备好,下一次尝试又按呈现间隔安排。准备经过几个阶段,就可能接连支付几个完整等待周期。这些等待发生在成功绘制之前,只看 render+swap 的耗时解释不了帧率。
所以排查先区分成功 swap、没有结果的尝试和 worker 准备,保留暂停时的零绘制、零 swap 检查。否则,一次修改只是让引擎更频繁空转,也可能看起来像调度“更积极”了。
重试围着成功呈现计时
第一步把 FPS 限制的起点固定在最后一次成功 swap。Scene 准备未完成时,按 8 ms 重试;空尝试不会把呈现时钟重新往后推。Video 继续保留自己的 decoder 重试策略。
同一轮还处理了动画区域的缓存身份。原实现为了生成键,要再次扫描已经解码好的 RGBA;区域解码本身是确定的,可以由封闭编码内容的摘要、准备来源、帧号、准确矩形和版本契约推导身份。来源验证、BC3 解码和像素上传照常执行,省掉的是重复扫描。
这轮改动的配对记录为 5.8833 → 9.7833 FPS。它也有代价:尝试次数增加,采样得到的引擎 CPU 占用上升。这个结果能说明等待安排影响了吞吐,不能说明功耗下降,更不能把“成功 render+swap 很短”解释为整帧已经很轻。
结果准备好,就唤醒引擎
8 ms 重试解决了整段呈现间隔的叠加,仍可能让刚准备好的结果再等一次轮询。下一步让 worker 发布结果后主动通知事件循环,同时保留 8 ms 兜底。
通知属于具体的 renderer。旧代迟到的 ping 不能让新代误以为结果已就绪,同一代连续通知也会合并。通知只移除尚未完成的 Scene 准备等待,不能绕过成功 swap 对应的 FPS 截止时间。暂停和静态空闲的行为也要继续成立。
这一轮顺便检查了 BC3 区域解码:内部完整块不必为每个像素重复计算裁切,直接复制固定的四行;边缘块继续走原有裁切。整数解码公式与输出范围保留。worker 唤醒改动因此同时有调度和 CPU 解码两部分,不能把整轮收益都记在某一个函数名下。
过期结果也值得单独看。候选被取消以后,后台工作仍可能刚好完成;收到通知只代表有事可查,不能直接接受那个结果。代次隔离和 packet 标签要继续参与判断。否则短测试里偶尔省下的一次等待,可能换来切换或回滚时消费错误一帧。
等待呈现时准备下一帧
主动唤醒以后,还有一段等待可以利用:引擎原先等 compositor 回调和 FPS 条件都满足,才开始下一帧 CPU 工作。准备和呈现等待几乎排成了串行。
后续让现有 worker 在等待期间准备一个下一帧 packet。脚本、经过时间、音频与纹理仍按同一组标签传递,真正消费结果、上传和绘制仍从原呈现入口经过。提前做的是 CPU 准备,已接纳状态和历史不能提前推进。
范围也刻意收窄。单 session 才允许提前做 timed 准备;多 session 取消这部分推测任务,保留受呈现条件约束的解码,避免某个输出迟迟收不到回调却一直占住全局生产许可。暂停、事务切换和未确认 swap 也各有阻挡条件。
这次比较记录了 11.68 → 14.73 FPS。这组数字有自己的配对基线,不能与上一轮拼成一条连续提升曲线。保留下来的原始输出与历史像素比较,则用于检查吞吐改变以后,画面语义有没有跟着变。
GPU 侧只改已经确定的状态
CPU 与等待理顺后,排查才进一步缩到 GPU 路径。固定 blend mode 只在完整源码与 uniform 元数据匹配 LWE 自有模板时特化,作者特效仍走原路径。这样可以保留作者浮点运算,避免为了更快先悄悄改画面。
历史复制还有一项重复工作:来源已经是验证过的 RenderTarget,每次 copy 却重新创建、挂接、检查和删除一个临时 READ FBO。改动让 Named/History 来源复用已有 FBO,其他绑定仍保留临时路径。反馈和尺寸检查、复制顺序、分辨率与单次 NEAREST blit 都保留。
FBO 复用的验收记录为 14.71664 → 15.23330 FPS。幅度比前面的调度改动小,记录也没有把它解释成普遍收益。临时与复用路径的 RGBA、历史累积、暂停调整尺寸和错误拒绝都另有比较;一张最终截图相同,还不足以覆盖这些状态。
这些数字能说明多少
几轮原生比较使用 AMD 680M、嵌套 niri、1920×1080、完整默认效果和 30 FPS 上限,使用关闭 profiling 的 release。每个版本通常只有两个 30 秒动画窗口,按 A–B–B–A 穿插运行。它们是短时间观察,各轮基线不同,百分比不能相加;最后仍没有达到 30 FPS。
render+swap 是墙钟时间,前面的准备不在其中,也不是 GPU time。worker 通知延迟只量到事件循环收到提示,不能代替结果被消费的延迟。CPU 保留分配与 GPU 逻辑资源分别受 512 MiB 上限约束,这些账本不代表进程 RSS 或驱动 VRAM。
固定提交的贡献指南保留了比较条件与验证范围,对应 Quality Check也已通过。这组短时间原生引擎比较支持了调度与复制路径的改进;换成其他场景或更长的运行窗口,仍需重新测量,不能直接沿用这里的提升幅度。