【问题标题】:controlling FPS limit in OpenGL application在 OpenGL 应用程序中控制 FPS 限制
【发布时间】:2013-02-04 19:19:39
【问题描述】:

我正在尝试找到一种可靠的方法来准确设置我希望我的 OpenGL 应用程序在屏幕上呈现多少 FPS。我可以在某种程度上通过休眠 1000/fps 毫秒来做到这一点,但这并没有考虑到渲染所需的时间。 将 fps 限制在所需数量的最一致的方法是什么?

【问题讨论】:

  • 休眠 1 毫秒也不考虑操作系统调度程序的粒度。在大多数非实时操作系统上,您无法可靠地让线程/进程休眠 1 毫秒,您能做的最好的可能是 10-15 毫秒。所以你最终使用了一个自旋锁,它只是坐在那里浪费 CPU 周期。您不妨对物理模拟等进行双缓冲,并以与渲染不同的频率运行它,以保持 CPU 做一些有用的事情,同时满足应用程序渲染部分的调度截止日期。

标签: opengl 3d frame-rate


【解决方案1】:

您可以在 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");
}

【讨论】:

  • 这是同步到显示器刷新率的正确方法,这通常是您想要的。不幸的是,WGL(以及因此的 wglSwapIntervalEXT)是特定于 Windows 的。但是有一个等效的 GLX 扩展:opengl.org/registry/specs/EXT/swap_control.txt。如果你使用 EGL,还有 eglSwapInterval。
  • 这段代码怎么不好?这实际上是 方法
【解决方案2】:

由于 OpenGL 只是一个低级的图形 API,你不会在 OpenGL 中直接找到类似的东西。

但是,我认为您的逻辑有点缺陷。而不是以下内容:

  1. 画框
  2. 等待 1000/fps 毫秒
  3. 重复

你应该这样做:

  1. 启动计时器
  2. 画框
  3. 停止计时器
  4. 等待 (1000/fps - (stop - start)) 毫秒
  5. 重复

这样,如果您只等待您应该等待的数量,并且您最终应该非常接近每秒 60 帧(或任何您的目标)。

【讨论】:

  • 我在渲染函数的开头插入了 start = GetTickCount(),最后插入了 stop = GetTickCount()。在大约 99% 的情况下,这 2 个值相同,因此它们的差为 0。这是否意味着我的场景在不到一毫秒的时间内渲染?将此插入到我的睡眠计算中,fps 并不比以前更接近设定值。 Sleep(1000/fps) & Sleep(1000/fps -(stop-start)) 与我想要的 FPS 值的偏差相同。
  • 来自msdn.microsoft.com/en-us/library/windows/desktop/…,“GetTickCount 函数的分辨率受限于系统定时器的分辨率,通常在 10 毫秒到 16 毫秒的范围内。”
  • 如果您真的想自己构建它,请查看msdn.microsoft.com/en-us/magazine/cc163996.aspx,否则请使用 glut 或 wgl 之类的东西来为您构建。
  • 因为 OpenGL 是异步的,所以子帧计时可能非常棘手。最好的方法是计时整个帧并考虑到您的睡眠量。例如,存储lastTime,查找currentTimedeltaTime = currentTime - lastTimedrawingTime = deltaTime - sleptTimesleep(1.0/60.0 - drawingTime)sleptTime = 1.0/60.0 - drawingTimelastTime = currentTime
  • 测量 glFlush() 后的时间;并交换缓冲区,使用更准确的时间测量(QueryPerformanceCounter、RDTSC 或一些高分辨率计时器)
【解决方案3】:

OpenGL 本身没有任何允许限制帧速率的功能。期间。

但是,在现代 GPU 上,有很多功能涵盖帧速率、帧预测等。John Carmack 提出了一些问题,他推动了一些功能可用。还有 NVidia 的自适应同步。

这一切对你意味着什么?把它留给GPU。假设绘图是完全不可预测的(当您只坚持使用 OpenGL 时应该这样做),您自己为事件计时并将逻辑更新(例如物理)与绘图分开。这样用户就可以从所有这些先进技术中受益,而您不必再为此担心了。

【讨论】:

  • 但是你什么时候画画呢?如果我把它留给 OpenGL,我会得到 1000+ fps,因为我的屏幕只能以 60 fps 刷新,这是一种资源浪费..
  • 这个答案到底是怎么得到赏金的?虽然第一部分是正确的:OpenGL 本身 没有限制帧率的功能,但第二部分是完全错误的。非常希望启用 vsync(请注意,用户应该可以选择将其关闭)。这是通过 OpenGL 上下文 api(WGL、GLX、EGL 等),通过调用(例如)wglSwapIntervalEXT 来完成的。 每一款合适的游戏都使用这个。供应商后来甚至添加了negative swap intervals
  • @itzJanuary 您的链接是 404。我认为您误解了我所说的“将其留给 GPU”的意思。最初的问题是关于定时渲染以便以适当的速度处理逻辑。这是一个有根本缺陷的方法,也是我试图建议避免的。 直到(除非)渲染时间会导致它丢失帧,同步到屏幕的应用程序可能表现得相当好。关键仍然在于使逻辑与帧速率分离,无论是否使用任何同步。而且我不反对垂直同步。
  • 显然khronos决定取消sped,但也简要提到了here
  • 将逻辑与 draw 分开似乎是一个合理的想法,但在许多情况下没有办法实现这一点。单独的线程会导致各种同步问题(一个实体更新,一个不更新)。如果我没记错的话,所有主要的游戏引擎都在同一个循环和同一个线程中更新和绘制。该逻辑以增量时间调用,如果增量时间变得合理,甚至可能每帧调用两次。我似乎找不到解释虚幻引擎不同主循环类型的页面,但它们都是基于这个概念的变体
【解决方案4】:

不要使用睡眠。如果您这样做了,那么您的应用程序的其余部分必须等待它们完成。

相反,请跟踪已经过去了多少时间,并仅在达到 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;
}

【讨论】:

  • 如果你的应用程序很忙,那么你不应该在睡觉。如果没有什么可做的,那么你应该睡觉了。提及线程是个好主意,但渲染线程不能保持活动状态并使用 sleep() 作为计时器吗?
  • 如果你在一个单独的线程中渲染,那么使用 sleep() 会比在计时器上运行线程更好。
【解决方案5】:

把它放在绘制和调用交换缓冲区之后:

//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(假设时间以秒为单位)。

这不会为您提供垂直同步,但意味着您的应用不会失控,这可能会影响物理计算(如果它们不是固定步长)、动画等。请记住 处理输入之后睡眠!我尝试过尝试平均帧时间,但到目前为止效果最好。

对于getElapsedTimeOfChoice(),我使用提到的here,即

【讨论】:

    【解决方案6】:

    另一个想法是使用 WaitableTimers(如果可能,例如在 Windows 上)

    基本思路:

    while (true) 
    { 
        SetWaitableTimer(myTimer, desired_frame_duration, ...); 
        PeekMsg(...) 
        if (quit....) break; 
        if (msg)
            handle message; 
        else 
        { 
            Render(); 
            SwapBuffers();
        }
        WaitForSingleObject(myTimer); 
    }
    

    更多信息:How to limit fps information

    【讨论】:

      【解决方案7】:

      一种简单的方法是使用 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);
      }
      

      【讨论】:

        猜你喜欢
        • 2011-10-06
        • 2021-08-30
        • 1970-01-01
        • 1970-01-01
        • 2013-11-14
        • 2020-03-30
        • 1970-01-01
        • 2013-03-14
        • 1970-01-01
        相关资源
        最近更新 更多