我正在尝试找到一种可靠的方法来准确设置我希望我的 OpenGL 应用程序在屏幕上呈现多少 FPS。我可以在某种程度上通过休眠 1000/fps 毫秒来做到这一点,但这并没有考虑到渲染所需的时间。将 fps 限制在所需数量的最一致的方法是什么?
7 回答
您可以在 opengl 中使用 wglSwapIntervalEXT 同步到 vblank。它不是很好的代码,但它确实有效。
http://www.gamedev.net/topic/360862-wglswapintervalext/#entry3371062
bool WGLExtensionSupported(const char *extension_name) {
PFNWGLGETEXTENSIONSSTRINGEXTPROC _wglGetExtensionsStringEXT = NULL;
_wglGetExtensionsStringEXT = (PFNWGLGETEXTENSIONSSTRINGEXTPROC)wglGetProcAddress("wglGetExtensionsStringEXT");
if (strstr(_wglGetExtensionsStringEXT(), extension_name) == NULL) {
return false;
}
return true;
}
和
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = NULL;
if (WGLExtensionSupported("WGL_EXT_swap_control"))
{
// Extension is supported, init pointers.
wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
// this is another function from WGL_EXT_swap_control extension
wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC)wglGetProcAddress("wglGetSwapIntervalEXT");
}
由于 OpenGL 只是一个低级的图形 API,因此您不会在 OpenGL 中直接找到类似的东西。
但是,我认为您的逻辑有点缺陷。而不是以下内容:
- 并条框
- 等待 1000/fps 毫秒
- 重复
你应该做这个:
- 启动计时器
- 并条框
- 停止计时器
- 等待 (1000/fps - (stop - start)) 毫秒
- 重复
这样,如果您只等待您应该等待的数量,并且您最终应该非常接近每秒 60(或任何您的目标)帧。
OpenGL 本身没有任何允许限制帧速率的功能。时期。
然而,在现代 GPU 上,有很多功能涵盖帧速率、帧预测等。John Carmack 提出了一些问题,他推动了一些功能可用。还有 NVidia 的自适应同步。
这一切对你意味着什么?把它留给GPU。假设绘图是完全不可预测的(当您只坚持使用 OpenGL 时应该这样做),您自己为事件计时并将逻辑更新(例如物理)与绘图分开。这样,用户将能够从所有这些先进技术中受益,而您不必再担心这一点。
不要使用睡眠。如果你这样做了,那么你的应用程序的其余部分必须等待它们完成。
相反,跟踪已经过去了多少时间并仅在达到 1000/fps 时才进行渲染。如果没有达到计时器,跳过它并做其他事情。
在单线程环境中,很难确保您以 1000/fps 的速度精确绘制,除非这绝对是您唯一要做的事情。一种更通用和更强大的方法是将所有渲染都在一个单独的线程中完成,并在计时器上启动/运行该线程。这是一个更复杂的问题,但会让你最接近你的要求。
此外,跟踪发布渲染需要多长时间将有助于即时调整渲染的时间。
static unsigned int render_time=0;
now = timegettime();
elapsed_time = last_render_time - now - render_time;
if ( elapsed_time > 1000/fps ){
render(){
start_render = timegettime();
issue rendering commands...
end_render = timegettime();
render_time = end_render - start_render;
}
last_render_time = now;
}
把它放在绘制和调用交换缓冲区之后:
//calculate time taken to render last frame (and assume the next will be similar)
thisTime = getElapsedTimeOfChoice(); //the higher resolution this is the better
deltaTime = thisTime - lastTime;
lastTime = thisTime;
//limit framerate by sleeping. a sleep call is never really that accurate
if (minFrameTime > 0)
{
sleepTime += minFrameTime - deltaTime; //add difference to desired deltaTime
sleepTime = max(sleepTime, 0); //negative sleeping won't make it go faster :(
sleepFunctionOfChoice(sleepTime);
}
如果你想要 60fps,minFrameTime = 1.0/60.0
(假设时间以秒为单位)。
这不会为您提供 vsync,但意味着您的应用程序不会失控,这可能会影响物理计算(如果它们不是固定步长)、动画等。请记住在睡眠后处理输入!我尝试过尝试平均帧时间,但到目前为止效果最好。
因为getElapsedTimeOfChoice()
,我使用这里提到的内容,即
另一个想法是使用 WaitableTimers (如果可能,例如在 Windows 上)
基本思想:
while (true)
{
SetWaitableTimer(myTimer, desired_frame_duration, ...);
PeekMsg(...)
if (quit....) break;
if (msg)
handle message;
else
{
Render();
SwapBuffers();
}
WaitForSingleObject(myTimer);
}
更多信息:如何限制 fps 信息
一个简单的方法是使用 GLUT。这段代码可以大致完成这项工作。
static int redisplay_interval;
void timer(int) {
glutPostRedisplay();
glutTimerFunc(redisplay_interval, timer, 0);
}
void setFPS(int fps)
{
redisplay_interval = 1000 / fps;
glutTimerFunc(redisplay_interval, timer, 0);
}