【问题标题】:timeGetTime() start variable is bigger than end variabletimeGetTime() 开始变量大于结束变量
【发布时间】:2015-09-19 16:08:09
【问题描述】:

我正在使用timeGetTime() 将帧速率限制为每秒 60 帧。我打算这样做的方法是获取渲染所述 60 帧所需的时间,然后使用 Sleep 等待第二秒的剩余时间。但由于某种原因,timeGetTime() 第一次调用它时返回的数字比我在渲染 60 帧后调用它时返回的数字要大。

代码如下:

标题

#ifndef __TesteMapa_h_
#define __TesteMapa_h_
#include "BaseApplication.h"
#include "Mundo.h"

class TesteMapa : public BaseApplication{
public:
    TesteMapa()
    virtual ~TesteMapa();

protected:
    virtual void createScene();

    virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
    virtual bool frameEnded(const Ogre::FrameEvent& evt);

    virtual bool keyPressed(const OIS::KeyEvent &evt);
    virtual bool keyReleased(const OIS::KeyEvent &evt);

private:
    Mundo mundo = Mundo(3,3,3);
    short altura, largura, passos, balanca, framesNoSegundo=0;
    Ogre::SceneNode *noSol, *noSolFilho, *noCamera;
    DWORD inicioSegundo = 0, finala;//inicioSegundo is the start variable and finala the ending variable
};
#endif 

CPP相关功能

bool TesteMapa::frameEnded(const Ogre::FrameEvent& evt){
    framesNoSegundo++;

    if (inicioSegundo == 0)
        inicioSegundo = timeGetTime();
    else{
        if (framesNoSegundo == 60){
            finala = timeGetTime(); //getting this just to see the value being returned
            Sleep(1000UL - (timeGetTime() - inicioSegundo));
            inicioSegundo = 0;
            framesNoSegundo = 0;
        }
    }
    return true;
}

我在主函数中使用timeBeginPeriod(1)timeEndPeriod(1)

【问题讨论】:

  • 嗯,不,这绝不是操作系统中的错误。那个 Sleep() 调用很无聊。当 60 帧花费超过一秒的时间时,它将休眠很长时间。他们通常会这样做。
  • “60 帧”不是帧速率。我对你在问什么感到困惑。
  • 您确定它会返回“更大的价值”吗?我的意思是,如果您的 60 帧花费超过 1000 毫秒,那么您最终将基本上永远处于休眠状态,因为您使用的是无符号类型。除了这是一种限制帧率的奇怪方式之外,我会使用 std::chrono::steady_clock 而不是特定于 Windows 的函数。
  • @LightnessRacesinOrbit 我会编辑它。
  • 首先,它是标准的一部分并且是跨平台的。其次,(我猜在你的情况下更重要的是)它具有表示时间和持续时间的特定类型(std::chrono::time_pointstd::chrono::duration,而不是通用DWORD),这可以最大限度地减少编写相关代码时可能犯的潜在错误时间测量。

标签: c++ windows ogre


【解决方案1】:

甚至没有阅读完整的问题,以下内容:

使用 timeGetTime() t 将帧速率限制为每秒 60 帧
...
在剩下的时间里睡觉

可以用坚定的“你做错了”来回答。换句话说,在这里停下来,采取不同的方法。

timeGetTime 也没有必要的精度(即使你使用timeBeginPeriod(1)),Sleep 也没有所需的精度,Sleep 也没有提供关于最长持续时间的任何保证,语义也没有的Sleep 甚至远远接近您的预期,睡眠也不是限制帧速率的正确方法。
此外,计算秒数的余数将不可避免地引入系统误差,该误差会随着时间的推移而累积。

限制帧速率的唯一正确方法是使用垂直同步。

如果您需要将模拟限制为特定速率,则使用等待计时器是正确的方法。这仍然取决于调度程序的精度,但它会避免累积系统错误,并且优先级提升将至少提供事实上的软实时保证。

为了理解为什么您所做的事情(除了精度和累积误差)在概念上是错误的,请考虑两件事:

  1. 不同的计时器,即使它们以明显相同的频率运行,也会产生分歧(因此,使用除垂直同步中断之外的任何计时器错误来限制帧速率)。在红灯处观察汽车,以进行现实生活中的类比。他们的闪光灯会总是不同步。
  2. Sleep 使当前线程“未准备好”运行,最终,在指定时间过去一段时间后,使线程再次“准备好”。这并不意味着该线程将在那个时候再次运行。实际上,这并不一定意味着线程会在任何有限的时间内运行。
  3. 分辨率通常在 16 毫秒左右(如果您调整调度程序的粒度,则为 1 毫秒,这是一种反模式 - 一些最近的架构通过使用未记录的 Nt API 支持 0.5 毫秒),这对于 1/60 秒的某些东西来说太粗糙了规模。

【讨论】:

  • 这个答案的问题是我想将游戏限制为 60 fps,如果显示器的刷新率较高,游戏将以较高的值运行。但是,是的,我的方法是错误的。谢谢。
  • 您仍然可以在 60Hz 下运行逻辑(实际上,对于任何诸如积分之类的事情,固定时间步长是可取的,实际上是必不可少的)并插入您在屏幕上呈现的内容。但是您呈现的内容仍然必须与正确的计时器(vsync 中断)同步,而不是与其他任何东西同步。另外,由于调度程序的精度有限,您可能希望有一个 6Hz 的计时器并一次运行 10 帧的逻辑,而不是 60Hz 的计时器。或者更好的是,根本没有计时器,而是根据需要提供状态的生产者(由使用 vsync 的渲染线程消耗)。
  • 例如,您可以始终将一个固定时间的快照渲染到未来,并在此和最后一个(例如 1/10 秒)之间进行插值,并且每当插值达到一次未来现在存在状态时,您向生产者发出信号以生成另一个快照(然后您将从下一帧开始使用它进行插值)。
【解决方案2】:

如果您使用的是 Visual Studio 2013 或更早版本,std::chrono 使用 64hz 代码(每滴答声 15.625 毫秒),这很慢。 VS 2015 应该解决这个问题。您可以改用 QueryPerformanceCounter。这是以固定频率运行且没有漂移的示例代码,因为延迟基于计数器的原始读数。 dwLateStep 是一种调试辅助工具,如果一个或多个步骤花费的时间过长,它会增加。该代码与 Windows XP 兼容,其中 Sleep(1) 最多可能需要 2 毫秒,这就是为什么代码仅在有 2 毫秒或更多时间延迟时才会休眠的原因。

typedef unsigned long long UI64;        /* unsigned 64 bit int */
#define FREQ    60                      /* frequency */
DWORD    dwLateStep;                    /* late step count */
LARGE_INTEGER liPerfFreq;               /* 64 bit frequency */
LARGE_INTEGER liPerfTemp;               /* used for query */
UI64 uFreq = FREQ;                      /* thread frequency */
UI64 uOrig;                             /* original tick */
UI64 uWait;                             /* tick rate / freq */
UI64 uRem = 0;                          /* tick rate % freq */
UI64 uPrev;                             /* previous tick based on original tick */
UI64 uDelta;                            /* current tick - previous */
UI64 u2ms;                              /* 2ms of ticks */
UI64 i;

    /* ... */ /* wait for some event to start thread */
    QueryPerformanceFrequency(&liPerfFreq);
    u2ms = ((UI64)(liPerfFreq.QuadPart)+499) / ((UI64)500);

    timeBeginPeriod(1);                 /* set period to 1ms */
    Sleep(128);                         /* wait for it to stabilize */

    QueryPerformanceCounter(&liPerfTemp);
    uOrig = uPrev = liPerfTemp.QuadPart;

    for(i = 0; i < (uFreq*30); i++){
        /* update uWait and uRem based on uRem */
        uWait = ((UI64)(liPerfFreq.QuadPart) + uRem) / uFreq;
        uRem  = ((UI64)(liPerfFreq.QuadPart) + uRem) % uFreq;
        /* wait for uWait ticks */
        while(1){
            QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
            uDelta = (UI64)(liPerfTemp.QuadPart - uPrev);
            if(uDelta >= uWait)
                break;
            if((uWait - uDelta) > u2ms)
                Sleep(1);
        }
        if(uDelta >= (uWait*2))
            dwLateStep += 1;
        uPrev += uWait;
        /* fixed frequency code goes here */
        /*  along with some type of break when done */
    }

    timeEndPeriod(1);                   /* restore period */

【讨论】:

  • 谢谢。我确实在使用 VS2013。我将尝试 QueryPerformanceCounter 方法。
猜你喜欢
  • 2018-12-20
  • 1970-01-01
  • 2021-05-16
  • 2020-04-03
  • 2020-03-21
  • 2016-10-31
  • 2020-05-31
  • 1970-01-01
  • 2018-11-07
相关资源
最近更新 更多