11

我目前正在开发一个 OpenGL 应用程序,以向用户显示一些 3D 球体,他们可以旋转、移动等。话虽如此,这里的复杂性并不多,因此应用程序的运行速度相当高帧率(〜500 FPS)。

显然,这太过分了——即使 120 也足够了,但我的问题是,以全状态运行应用程序会消耗我的 CPU,导致过热、功耗等。我想要做的是能够让用户设置了 FPS 上限,以便在不需要时不会过度使用 CPU。

我正在使用 freeglut 和 C++,并且已经设置了动画/事件处理以使用计时器(使用glutTimerFunc)。但是glutTimerFunc,只允许设置整数毫秒数 - 所以如果我想要 120 FPS,我能得到的最接近的是(int)1000/120 = 8 ms分辨率,相当于 125 FPS(我知道这是一个可以忽略不计的数量,但我仍然只想放如果我知道系统可以更快地渲染,则在 FPS 限制中准确地获得该 FPS)。

此外,使用glutTimerFunc来限制 FPS 永远不会始终如一地工作。假设我将我的应用程序限制为 100 FPS,它通常永远不会高于 90-95 FPS。同样,我试图计算出渲染/计算之间的时间差,但它总是超出限制 5-10 FPS(可能是计时器分辨率)。

我想这里最好的比较将是一个游戏(例如半条命2) - 你设置你的FPS上限,它总是达到那个确切的数量。我知道我可以在渲染每一帧之前和之后测量时间增量,然后循环直到我需要绘制下一帧,但这并不能解决我的 100% CPU 使用率问题,也不能解决时间分辨率问题。

有什么方法可以在我的应用程序中实现有效的、跨平台的、可变帧速率限制器/上限?或者,以另一种方式,是否有任何跨平台(和开源)库可以实现高分辨率计时器和睡眠功能?

编辑:我希望找到一个不依赖最终用户启用 VSync 的解决方案,因为我将让他们指定 FPS 上限。

编辑#2:对于所有推荐 SDL的人(我最终将我的应用程序移植到 SDLglutTimerFunc ),使用该函数触发抽奖或SDL_Delay在抽奖之间等待有什么区别?每个文档都提到了相同的警告,但我不确定一个是否比另一个效率更高或更低。

编辑#3:基本上,我试图弄清楚是否有(简单的方法)在我的应用程序中实现准确的 FPS 限制器(再次,如半条命 2)。如果这不可能,我很可能会切换到 SDL(对我来说,使用延迟函数而不是每毫秒glutTimerFunc回调渲染函数更有意义)。x

4

6 回答 6

4

我建议您使用 SDL。我个人用它来管理我的计时器。此外,它可以使用 SDL 1.3 将您的 fps 限制为屏幕刷新率 (V-Sync)。这使您能够在获得最佳屏幕性能的同时限制 CPU 使用率(即使您有更多帧,它们也无法显示,因为您的屏幕刷新速度不够快)。

功能是

SDL_GL_SetSwapInterval(1);

如果你想要一些使用 SDL 的计时器代码,你可以在这里看到:

我的计时器课

祝你好运 :)

于 2011-07-15T13:31:59.283 回答
4

无论您使用什么图形库,我认为实现这一点的一个好方法是在游戏循环中进行单个时钟测量,以考虑每个滴答声(毫秒)。这样一来,平均 fps 将正好是《半条命 2》中的极限。希望下面的代码片段能解释我在说什么:

//FPS limit
unsigned int FPS = 120;

//double holding clocktime on last measurement
double clock = 0;

while (cont) {
    //double holding difference between clocktimes
    double deltaticks;

    //double holding the clocktime in this new frame
    double newclock;

    //do stuff, update stuff, render stuff...

    //measure clocktime of this frame
    //this function can be replaced by any function returning the time in ms
    //for example clock() from <time.h>
    newclock = SDL_GetTicks();

    //calculate clockticks missing until the next loop should be
    //done to achieve an avg framerate of FPS 
    // 1000 / 120 makes 8.333... ticks per frame
    deltaticks = 1000 / FPS - (newclock - clock);

    /* if there is an integral number of ticks missing then wait the
    remaining time
    SDL_Delay takes an integer of ms to delay the program like most delay
    functions do and can be replaced by any delay function */
    if (floor(deltaticks) > 0)
        SDL_Delay(deltaticks);

    //the clock measurement is now shifted forward in time by the amount
    //SDL_Delay waited and the fractional part that was not considered yet
    //aka deltaticks
    the fractional part is considered in the next frame
    if (deltaticks < -30) {
        /*dont try to compensate more than 30ms(a few frames) behind the
        framerate
        //when the limit is higher than the possible avg fps deltaticks
        would keep sinking without this 30ms limitation
        this ensures the fps even if the real possible fps is
        macroscopically inconsitent.*/
        clock = newclock - 30;
    } else {
        clock = newclock + deltaticks;
    }

    /* deltaticks can be negative when a frame took longer than it should
    have or the measured time the frame took was zero
    the next frame then won't be delayed by so long to compensate for the
    previous frame taking longer. */


    //do some more stuff, swap buffers for example:
    SDL_RendererPresent(renderer); //this is SDLs swap buffers function
}

我希望这个 SDL 示例有所帮助。重要的是每帧只测量一次时间,因此要考虑每一帧。我建议在一个函数中模块化这个时间,这也使你的代码更清晰。这段代码没有评论,因为他们只是在最后一个惹恼了你:

unsigned int FPS = 120;

void renderPresent(SDL_Renderer * renderer) {
    static double clock = 0;
    double deltaticks;
    double newclock = SDL_GetTicks();

    deltaticks = 1000.0 / FPS - (newclock - clock);

    if (floor(deltaticks) > 0)
        SDL_Delay(deltaticks);

    if (deltaticks < -30) {
        clock = newclock - 30;
    } else {
        clock = newclock + deltaticks;
    }

    SDL_RenderPresent(renderer);
}

现在你可以在你的 mainloop 中调用这个函数,而不是你的 swapBuffer 函数(SDL_RenderPresent(renderer)在 SDL 中)。在 SDL 中,您必须确保该SDL_RENDERER_PRESENTVSYNC标志已关闭。此函数依赖于全局变量FPS,但您可以考虑其他存储方式。我只是把整个东西放在我图书馆的命名空间里。


如果由于 30ms 限制为deltaticks. deltaticks限制是必需的。当 FPS 限制高于实际帧率deltaticks时会无限下降。同样,当帧速率再次上升到 FPS 限制之上时,代码将尝试通过立即渲染每一帧来补偿损失的时间,从而导致巨大的帧速率,直到deltaticks上升回零。您可以修改 30ms 以满足您的需求,这只是我的估计。我用 Fraps 做了几个基准测试。它确实适用于所有可以想象的帧速率,并根据我的测试提供漂亮的结果。


我必须承认我昨天才编写了这个代码,所以不太可能有某种错误。我知道这个问题是 5 年前提出的,但给出的答案并没有说明我的情况。也可以随意编辑这篇文章,因为这是我的第一篇文章,可能有缺陷。

编辑:我注意到SDL_Delay在某些系统上非常非常不准确。我听说过一个在 android 上延迟太多的案例。这意味着我的代码可能无法移植到您想要的所有系统。

于 2016-09-22T15:04:26.687 回答
3

解决它的最简单方法是启用 Vsync。这就是我在大多数游戏中所做的,以防止我的笔记本电脑过热。只要您确保渲染路径的速度没有连接到其他逻辑,这应该没问题。

有一个函数 glutGet( GLUT_ELAPSED_TIME ) 以毫秒为单位返回自启动以来的时间,但这可能还不够快。

一种简单的方法是制作自己的计时器方法,该方法在 Windows 上使用 HighPerformanceQueryTimer,在 POSIX 系统上使用 getTimeOfDay。

或者,您始终可以使用 SDL 或 SFML 中的计时器函数,它们的作用与上述基本相同。

于 2011-07-15T13:33:49.660 回答
3

您不应尝试手动限制渲染速率,而应与显示垂直刷新同步。这是通过在图形驱动程序设置中启用 V 同步来完成的。除了防止(您的)程序以高速率渲染外,它还通过避免撕裂来提高图像质量。

交换间隔扩展允许您的应用程序微调 V 同步行为。但在大多数情况下,只需在驱动程序中启用 V 同步并让缓冲区交换块直到同步就足够了。

于 2011-07-15T13:52:06.883 回答
0

我建议使用亚毫秒精度的系统计时器(QueryPerformanceCounter、gettimeofday)来获取计时数据。这些也可以帮助您在优化的发布版本中分析性能。

于 2013-01-01T21:13:42.040 回答
0

一些背景资料:

  • SDL_Delay 与 Sleep/sleep/usleep/nanosleep 几乎相同,但它被限制为毫秒作为参数
  • 睡眠依靠系统线程调度程序来继续你的代码。
  • 根据您的操作系统和硬件,调度程序的滴答频率可能低于 1000hz,这会导致时间跨度比您在调用 sleep 时指定的要长,因此您无法保证获得所需的 sleep time
  • 您可以尝试更改调度程序的频率。在 Windows 上,您可以通过调用timeBeginPeriod()来完成。对于 linux 系统,请查看此答案
  • 即使您的操作系统支持 1000hz 的调度程序频率,您的硬件也可能不支持,但大多数现代硬件支持。
  • 即使您的调度程序的频率为 1000hz,如果系统正忙于更高优先级的进程,睡眠可能需要更长的时间,但如果您的系统不是处于超高负载下,则不应发生这种情况。

总而言之,您可能会在一些无滴答 linux 内核上休眠几微秒,但如果您对跨平台解决方案感兴趣,您应该尝试将调度程序频率提高到 1000hz,以确保在大多数情况下休眠是准确的。

要解决 120FPS 的舍入问题:

1000/120      = 8,333ms
(int)1000/120 = 8ms

要么你忙等待333 微秒,然后睡 8 毫秒。这会花费一些 CPU 时间,但非常准确。或者你遵循 Neop 方法,有时睡 8 毫秒,有时睡 9 毫秒,平均为 8,333 毫秒。哪种方式更有效但不太准确。

于 2018-05-20T08:23:51.720 回答