我知道glutMainLoop()用于一遍又一遍地调用显示,以保持恒定的帧速率。同时,如果我也有glutTimerFunc(),它在末尾调用glutPostRedisplay(),因此它可以保持不同的帧速率。
当他们一起工作时,真正发生了什么?计时器功能是否会增加主循环的帧速率并使其更快?还是会更改主循环的默认刷新率?它们如何协同工作?
最佳答案
不!那不是glutMainLoop
所做的。 glutMainLoop
的目的是提取操作系统事件,检查计时器是否已过去,查看是否必须重绘窗口,然后调用用户注册的相应回调函数。这发生在一个循环中,通常该循环从程序的主入口点开始,因此命名为“main-loop”。
如前所述,调度计时器是glutMainLoop
的职责之一,因此,如果没有GLUT计时器,您将无法使用它。更重要的是,如果未发生任何事件且未发布任何重新显示,并且未注册空闲函数,则glutMainLoop
将“阻止”程序,直到发生一些有趣的事情(即不消耗CPU周期)。
本质上它就像
void glutMainLoop(void)
{
for(;;){
/* ... */
foreach(t in timers){
if( t.elapsed() ){
t.callback(…);
continue;
}
}
/* ... */
if( display.posted ){
display.callback();
display.posted = false;
continue;
}
idle.callback();
}
}
GLUT提供的计时器不能保证其精度和抖动。因此,它们并不是特别适合于帧速率限制。
通常,帧速率受垂直同步的限制(或应该受到垂直同步的限制),但垂直同步的阻止意味着您无法使用该时间来执行一些有用的操作,因为该过程已被阻止。更好的方法是注册一个空闲函数,在该函数中,您轮询一个高分辨率计时器(在POSIX兼容系统上
clock_gettime(CLOCK_MONOTONIC, …)
上,在Windows上QueryPerformanceCounter
上),并在一个显示刷新间隔减去渲染帧所需的时间后执行glutPostRedisplay
。当然,很难准确预测渲染将花费多长时间,因此通常的方法是收集滑动窗口的平均值和偏差并据此进行调整。另外,您还想使该计时器与v-sync对齐。
当然,这是一个已解决的问题(至少在电气工程中),可以通过锁相环解决。本质上,您有一个“相位比较器”(即可以比较您的计时器比您要同步的时钟慢还是快),一个“电荷泵”(您在相位比较器的增量中增加或减去的变量),由电荷泵中的环路滤波值控制的“环路滤波器”(滑动窗口平均值)和“振荡器”(计时器)。
因此,您轮询了v-sync的状态(GLUT功能无法实现,核心OpenGL甚至某些交换控制扩展甚至都无法实现–您必须使用OS特定的功能),并比较计时器是否正常相比之下落后或跑得快。您可以将该增量添加到“电荷泵”中,对其进行过滤,然后将结果反馈到计时器中。这种方法的好处是,它可以自动调整并过滤渲染帧所花费的时间。