游戏循环中的Win32 Sleep()准确度

时间:2018-02-19 20:05:24

标签: c++ c winapi

我有一个基本的固定时间步骤游戏循环,看起来像

// somewhere in source...
f32 Win32_ElapsedSeconds(LARGE_INTEGER Start, LARGE_INTEGER End)
{
    f32 Result = ((f32)(End.QuadPart - Start.QuadPart)) / GlobalPerfCounterRate;
    return Result;
}

// Get the monitor refresh rate, etc... Let's assume 60Hz for simpler code.
f32 TargetFrameRate = 1.0f / 60.0f;
LARGE_INTEGER LastTimeCounter = Win32_GetCurrentCounter();

// Set the timer's resolution to be more accurate. We need 1ms at least!
UINT DesiredSchedulerMS = 1;
GlobalGranularSleeping = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR);

LARGE_INTEGER PerfCountRate;
QueryPerformanceFrequency(&PerfCountRate);
global f32 GlobalPerfCounterRate = PerfCountRate.QuadPart;

while (1)
{
    f32 ElapsedSeconds = Win32_ElapsedSeconds(LastTimeCounter, Win32_GetCurrentCounter());
    if (ElapsedSeconds < TargetFrameRate) {

#if 1
        if (GlobalGranularSleeping) {
            DWORD TimeMS = (DWORD) (1000.0f * (TargetFrameRate - ElapsedSeconds));
            if (TimeMS > 0) {
                Sleep(TimeMS);   
            }
        }
#endif

        f32 TestElapsedSeconds = Win32_ElapsedSeconds(LastTimeCounter, Win32_GetCurrentCounter());
        if (TestElapsedSeconds > TargetFrameRate)
        {
            DebugWrite("Oversleeping!\n");
        }

        while(ElapsedSeconds < TargetFrameRate) {
            // Remain idle until we hit the next frame
            ElapsedSeconds = Win32_ElapsedSeconds(LastTimeCounter, Win32_GetCurrentCounter());
        }

    } else {
        DebugWrite("Dropped frame!\n");
    }

    LARGE_INTEGER EndTimeCounter = Win32_GetCurrentCounter();
    s64 TimeCounterElapsed = EndTimeCounter.QuadPart - LastTimeCounter.QuadPart;        
    LastTimeCounter = Win32_GetCurrentCounter();

    f32 FPS = (f32)GlobalPerfCounterRate / TimeCounterElapsed;

    char Buff[10];
    sprintf(Buff, "%f\n", FPS);
    DebugWrite(Buff);
}

输出:

Oversleeping!
58.686577
Oversleeping!
58.927273
Oversleeping!
58.862743
Oversleeping!
58.702045
Oversleeping!
58.765110
Oversleeping!
58.619267
Oversleeping!
58.711994
Oversleeping!
58.787273
Oversleeping!
58.910568
59.998901
59.998901
Oversleeping!
58.464279
Oversleeping!
58.406231
Oversleeping!
58.685471
Oversleeping!
59.158722
Oversleeping!
58.601639
Oversleeping!
58.280674
Oversleeping!
58.634701
59.998901
59.998901
59.998901
Oversleeping!
59.125065
Oversleeping!
58.776192
Oversleeping!

问题是Sleep()有时会过度睡眠,即使粒度设置为1ms(断言被触发)。 所以我的问题是,即使Windows保证Sleep的粒度至少为1ms,它是否有可能睡过头?或者我的代码错了......

1 个答案:

答案 0 :(得分:4)

  

即使windows保证Sleep的粒度为1ms   至少

没有这样的保证。相反,实际时间四舍五入到最接近的(ceil)系统时钟间隔。

  

如果dwMilliseconds大于一个tick但小于2,则等待   可以是一到两个刻度之间的任何位置,依此类推。

MSDN

通常在游戏中,您可以在动画帧事件上执行操作 - 在显式VSync事件或未提交的WM_PAINT&#34; s - WM_PAINT事件处理程序中,在Windows上不调用BeginPaint/EndPaint()。所有这些都取决于您的平台。