Pyglet窗口无法达到60FPS,仅更新函数间隔小于1/60秒时正常是特性还是vsync问题?
两个帧率数值差异的本质
pyglet.clock.get_fps()统计的是pyglet时钟调度任务的触发频率,和实际画面绘制帧率没有直接关联:你用schedule_interval注册的update函数、窗口输入事件(比如鼠标移动、键盘输入)都会唤醒事件循环,被计入该统计值,所以鼠标移动时数值会飙升是完全正常的表现。
而FPSDisplay统计的是on_draw回调的实际调用频率,这才是你屏幕上画面的真实更新速率。
调度间隔影响绘制帧率的原因
pyglet默认的事件循环逻辑是仅当有事件触发时才会执行重绘操作,重绘时机还会和显示器的VSYNC信号对齐:
当你设置schedule_interval(update, 1.0/60.0)时,调度间隔刚好和60Hz显示器的VSYNC周期(约16.67ms)完全一致,很容易出现调度任务触发时间刚好错过VSYNC窗口期的情况,此时事件循环会多等待一个VSYNC周期才执行重绘,最终就会出现稳定30FPS的表现。
你将调度间隔改为1/70秒(约14.28ms)后,调度任务会在每个VSYNC周期到来前提前触发,不会错过重绘窗口,自然就能对齐到60FPS的VSYNC节奏。
VSYNC设置不生效的原因
pyglet的vsync参数会被操作系统、显卡驱动的全局配置覆盖,大部分消费级显卡的默认驱动设置会强制开启VSYNC,不管应用层怎么设置都不会生效,这就是你调整vsync参数看不到帧率变化的核心原因。
你可以先打开显卡驱动的3D设置面板,将VSYNC选项修改为「由应用程序控制」,再运行你的测试代码,此时关闭vsync后FPSDisplay应该能显示数百的帧率,和时钟统计值对齐。
如果你的目标是稳定60FPS的绘制速率,有两个更可靠的实现方式:
- 不需要刻意调整调度间隔,直接注册一个空的高频调度任务唤醒事件循环即可,比如:
pyglet.clock.schedule_interval(lambda dt: None, 1/120)
- 如果不需要自定义update逻辑,直接使用pyglet内置的固定帧率运行接口即可。
注意不要将pyglet.clock.get_fps()的返回值作为绘制帧率的参考,该数值仅能用来验证调度任务的执行速率。
内容的提问来源于stack exchange,提问作者Bunshock

