MoWorld 一边操作一边出画面 / 帧率到底该看哪个数?

文章导读
MoWorld 界面上的帧率数字、手上感觉到的“卡不卡”、以及一次操作到画面出现要等多久,是三件不同的事。想回答“帧率到底该看哪个数”,得先确定你要衡量的是哪一种:是画面持续刷新的密度,还是一次交互的端到端延迟。MoWorld 这类「一边操作一边出画面」的场景,通常卡在生成链路而不是屏幕刷新,所以只盯界面上的 fps,很容易和体感对不上。
📋 目录
  1. A 先在界面上找出所有和速度有关的显示项
  2. B 用打点脚本记录一次交互的时间线
  3. C 验证打点记录本身是否可靠
  4. D 把脚本记录和界面数字放在一起对照
  5. E 换输入节奏再测一轮
A A

MoWorld 界面上的帧率数字、手上感觉到的“卡不卡”、以及一次操作到画面出现要等多久,是三件不同的事。想回答“帧率到底该看哪个数”,得先确定你要衡量的是哪一种:是画面持续刷新的密度,还是一次交互的端到端延迟。MoWorld 这类「一边操作一边出画面」的场景,通常卡在生成链路而不是屏幕刷新,所以只盯界面上的 fps,很容易和体感对不上。

判断方向:先把 MoWorld 界面上所有和速度相关的显示项逐个记下来——fps、延迟、生成耗时分别出现在哪里、是否随操作跳动;再用打点脚本记录一次交互的 t1(按下操作)、t2(画面首次变化)、t3(画面稳定),把 t3-t1 与界面数字对照,才能判断那个数代表端到端延迟、首帧延迟还是渲染刷新率。需要提醒的是,脚本触发和肉眼观察天然有误差,结论只在当前环境、当前输入节奏下成立。

先在界面上找出所有和速度有关的显示项

这一步不测任何东西,只做一件事:把 MoWorld 界面上真实存在的数字抄下来。不要凭印象补,界面上没有的项就不要写。推荐用一张临时表格,字段固定成下面这几列,边看边填:

  • 显示项原文:照着界面上的字样抄,比如 fps、frame、latency、延迟、耗时、gen time,不要改写成自己的说法。
  • 位置:窗口标题栏、右下角状态栏、悬浮面板、日志输出区,还是只在鼠标悬停时才出现。位置决定了它是不是全局统计。
  • 单位与量纲:ms 还是 s;fps 是频率,不是时长,后面不能直接和秒数比较。
  • 是否随操作变化:不动鼠标时数值稳不稳;按下操作的一瞬间是否跳动、跳多快回落。
  • 取样方式:看起来是瞬时值,还是滑动平均——瞬时值跳得厉害,平均值会掩盖尖峰。

做完这一步你通常会发现:界面上的 fps 是“每秒刷新多少次”,而延迟或耗时才是“一次操作等多久”。两者单位不同,不能互相替代。哪些项真实存在、哪些只是你以为是帧率,这一步就能分清。

用打点脚本记录一次交互的时间线

目的很朴素:把“感觉有点慢”换成三个能比较的时间点。下面是一段不依赖任何官方接口的通用骨架,只是结构参考,触发方式用最原始的手动按键,你可以照着改。核心是三个时刻——按下操作记 t1,画面首次出现变化记 t2,画面不再变化、稳定下来记 t3,最后落成 CSV。

MoWorld 一边操作一边出画面 / 帧率到底该看哪个数?
# 通用骨架,不依赖 MoWorld 的任何官方接口
import csv, time

rows = []
for i in range(5):                       # 同一操作重复 5 次
    input("准备好后回车 -> 立刻执行操作 (t1)")
    t1 = time.perf_counter()
    input("画面首次变化时回车 (t2)")
    t2 = time.perf_counter()
    input("画面稳定、不再变化时回车 (t3)")
    t3 = time.perf_counter()
    rows.append([i + 1, round(t1, 4), round(t2, 4), round(t3, 4),
                 round(t2 - t1, 4), round(t3 - t1, 4)])
    print("本次 首帧=%.4fs 稳定=%.4fs" % (t2 - t1, t3 - t1))

with open("moworld_timeline.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["run", "t1", "t2", "t3", "t2_minus_t1", "t3_minus_t1"])
    w.writerows(rows)

触发方式有两种,选一种就够:手动按键如上,简单但按键时刻和你“眼睛看到”的时刻有出入;录屏回放则是用固定帧率的屏幕录制,事后逐帧定位 t1、t2、t3,误差受限于单帧间隔,通常更接近肉眼判断。脚本只负责把秒数写进 CSV,别顺手把界面上的 fps 也塞进去,那是下一节要做的事。

验证打点记录本身是否可靠

脚本自己也会出错,所以先证明它可信,再拿它的结论说话。做法:同一个操作重复 5 次,同时用手机秒表或另一个计时器从 t1 掐到 t3,把秒表读数和 CSV 里的 t3_minus_t1 并排看。如果两列整体接近、只是每次差一点点,说明触发方式基本可用;如果偏差稳定地偏大或偏小一大截,先别怀疑 MoWorld,回头检查触发方式和观察方式是否一致——比如你的 t2 是“按回车的时刻”,而不是“眼睛看到画面变化的时刻”,中间夹着你的反应时间;录屏定位时,单帧间隔本身就会带来系统性偏移。

修正方式也很直接:固定一种触发方式,把观察点定义写死在同一句里(“看到画面第一个像素块变色就按”),然后重新记录 5 次。记录方式校准过之后再谈数值差异,否则后面所有对照都是在拿噪声比噪声。

把脚本记录和界面数字放在一起对照

现在把 CSV 里的两个区间和界面显示值摆在一起:t2_minus_t1 是操作到画面首次变化的等待,t3_minus_t1 是操作到画面稳定的总时长。界面上的 fps 是频率,比较前先换算成时间,即 1/fps 近似单帧间隔,再拿去跟 t2_minus_t1 或 (t3 - t2)/变化帧数 比。

MoWorld 一边操作一边出画面 / 帧率到底该看哪个数?

三种常见对照结果,各自的观察结论不同:

  • 完全匹配:某个界面数字和 t3_minus_t1 在多次重复里整体一致。通常说明它就是端到端延迟,可以用它替代脚本做粗看。
  • 只匹配其中一段:界面数字只和 t2_minus_t1 对上,那么它描述的是首帧等待,不包括后续画面继续补全的时间;反之若只和帧间隔对上,它是渲染刷新率,不反映你这次操作等了多久。这两种都会让人觉得“数字好看但手感一般”。
  • 完全不匹配:数值对不上任何一段,而且重复之间跳动很大。这时界面数字更可能是某个瞬时采样或高层的平均值,建议只用它看趋势,判断具体一次交互快慢还是以脚本记录为准。

换输入节奏再测一轮

拿同一个脚本,分别跑两轮:一轮慢速单步,每次操作之间停足几秒再动;一轮连续快速操作,尽量不留间隔。把两轮的 t2_minus_t1 和 t3_minus_t1 并排记录,观察数值是否随操作方式变化。

这里要留个心眼:两轮的差异不能直接归因于帧率本身。连续快速操作时,请求可能排队形成背压、输入可能被合并成一次处理、上下文复用的程度也和单步时不同,系统负载、热降频同样会掺进来。所以在记录表里额外写一列“本轮输入节奏 + 是否有其他程序占用”,差别明显时先排查这些因素,再去谈是不是刷新率的问题。慢速单步更接近“单次交互的真实等待”,连续快速操作更接近“持续压着用时的手感”,两者回答的不是同一个问题,最好都留一份。