2014-04-24 52 views
5

我正在编写一个C++/SDL/OpenGL应用程序,并且我有最奇怪的错误。游戏似乎在一个简单的可变时间步法下工作正常。但是,然后FPS开始表现奇怪。我发现Sleep(1)和SDL_Delay(1)需要15 ms才能完成。睡眠(1)和SDL_Delay(1)需要15 ms

任何输入到这些功能0-15之间需要15ms的完成,在大约64锁定FPS如果我将其设置为16,它需要30 MS O.O

我不知道为什么会这样。这是我遇到过的最奇怪的错误。

我的循环如下所示:

while (1){ 
    GLuint t = SDL_GetTicks(); 
    Sleep(1); //or SDL_Delay(1) 
    cout << SDL_GetTicks() - t << endl; //outputs 15 
} 

,因为它被认为它将很少需要1毫秒,但多数人的花费的时间为15ms。

我的操作系统是windows 8.1。 CPU是英特尔i7。我正在使用SDL2。任何想法将不胜感激,因为我很无能。

+2

[WinAPI Sleep()函数调用可能重复睡眠时间超过预期](http://stackoverflow.com/questions/9518106/winapi-sleep-function-睡眠时间超过预期) –

+0

如果您希望实时唤醒线程/进程,您不想让其进入睡眠状态。如果您不想担心计划,请使用螺旋锁。 –

回答

8

股票代码默认为64 hz或15.625 ms/tick。您需要使用timeBeginPeriod(1)将其更改为1000hz == 1ms。 MSDN文章:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx

如果这里的目标是获得一个固定频率的顺序,你应该使用更高分辨率的计时器,但不幸的是,这些只能被查询,所以轮询和睡眠的组合来降低CPU需要开销。示例代码假定Sleep(1)可能需要将近2毫秒(这在Windows XP中发生,但在Windows的后续版本中不会发生)。

/* code for a thread to run at fixed frequency */ 
#define FREQ 400      /* frequency */ 

typedef unsigned long long UI64;  /* unsigned 64 bit int */ 

LARGE_INTEGER liPerfTemp;    /* used for query */ 
UI64 uFreq = FREQ;      /* process 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 */ 
#if 0         /* for optional error check */ 
static DWORD dwLateStep = 0; 
#endif 

    /* wait for some event to start this thread code */ 
    timeBeginPeriod(1);     /* set period to 1ms */ 
    Sleep(128);       /* wait for it to stabilize */ 

    u2ms = ((UI64)(liPerfFreq.QuadPart)+499)/((UI64)500); 

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

    while(1){ 
     /* 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 0     /* optional error check */ 
     if(uDelta >= (uWait*2)) 
      dwLateStep += 1; 
     #endif 
     uPrev += uWait; 
     /* fixed frequency code goes here */ 
     /* along with some type of break when done */ 
    } 

    timeEndPeriod(1);     /* restore period */ 
+1

非常感谢!我没有意识到这一点! – user3346893

+0

由于uPrev基于自定时器的原始读数以来计算的滴答数,所以使用此方法随时间不会有任何漂移,而不依赖于定时器的当前读取和先前读取之间的增量。由于睡眠设置允许延迟2毫秒,因此示例代码应该适用于高达约400hz。如果不需要Windows XP支持,那么延迟可以假定Sleep(1)将花费大约1 ms(可能希望为此添加一些余量),在这种情况下,它应该适用于800hz。 – rcgldr

+0

如果固定周期是1ms的精确倍数,并且不需要Windows XP支持,则可以使用多媒体计时器事件功能。 MSDN文章:http://msdn.microsoft.com/en-us/library/windows/desktop/dd742877(v=vs.85).aspx。 Windows XP是一个问题,股票实际上会在1024hz下运行,1000hz是通过在42,42,然后41滴答中包括额外的滴答来模拟的,所以128个实际滴答以125个伪滴答结束(回到1ms边界) 。 – rcgldr

0

SDL_Delay()/的Sleep()不能与下面10-15毫秒的时间可靠地使用。 CPU ticks的注册速度不够快,无法检测到1 ms的差异。请参阅SDL docs here

+0

你是对的,谢谢我没有意识到这一点! – user3346893

+1

CPU ticks寄存器的速度足够快,真正的问题是当您将正在运行的线程休眠时发生的情况。它到达一个就绪队列的后面,然后它受到调度的支配。如果间隔足够小,一些框架足够聪明以避免这种行为,但显然这不是其中之一。或者,您可以将进程优先级设置为实时,并且该进程将在典型的10-15毫秒量程之前经常抢占其他进程。 –

1

看起来像15毫秒是操作系统将提供给您的最小片。我不确定你的具体框架,但睡眠通常保证最小的睡眠时间。 (即它将睡眠至少1ms)