3

这在程序员中似乎是一个巨大的秘密,没有人愿意为此分享他们的代码。为什么?

我找不到可以在不使用垂直同步的情况下将 FPS 至少限制为 60 的有效 FPS 限制器。

当然,我希望以正确的方式去做。所以我还没有自己做,因为他们都说他们花了一年的时间来学习 fps 限制器的所有技巧......

编辑:这是我的 fps 限制器代码,它并不完美,但我能做到最好,但它仍然流泪:

timeBeginPeriod(1);

frame_start_time = TimerGetTime();

while(!done){
    if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
        if(msg.message == WM_QUIT){
            done = 1;
        }else{
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }else if(active){ 
        draw_everything();
        SwapBuffers(hDC);

        // fps limiter:
        one_frame_limit = 1000.0f/(float)framerate_limit; // 16.666 ms
        while((time_left = one_frame_limit-(TimerGetTime()-frame_start_time)) > 0){
            if(time_left >= 6.0f){
                Sleep((int)(time_left/6.0f));
            }else{
                Sleep(0); // sleep less than 1ms
            }
        }
        frame_start_time = TimerGetTime();
    }
}

EDIT2:这是我的第二次尝试,按照建议使用等待计时器:

float one_frame_limit = 1000.0f/(float)framerate_limit;
float time_left = one_frame_limit-(TimerGetTime()-frame_start_time); // 4.7432ms
liDueTime.QuadPart = -(LONGLONG)(time_left*10000.0f);
if(SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0)){
    WaitForSingleObject(hTimer, INFINITE);
}
while((time_left = one_frame_limit-(TimerGetTime()-frame_start_time)) > 0.003f){
    Sleep(0);
}
frame_start_time = TimerGetTime();

我认为效果更好。但仍然在撕裂......请注意,我在那里添加了 while 循环,因为它比没有它更撕裂。

--

另一个问题:这个初始化安全吗?

HANDLE hTimer = NULL;
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = -100000LL; // testing timer: wait for 10ms

hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
if(hTimer == NULL){
    waitable_timer_supported = 0;
}else if(!SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0)){
    waitable_timer_supported = 0;
}else if(WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0){
    waitable_timer_supported = 0;
}

即使支持等待计时器,我担心最后 2 次检查可能会失败......到目前为止它还没有失败。这是检查其支持的正确方法吗?

4

3 回答 3

2

将系统计时器设置为每 16.6666 毫秒关闭一次,并在该事件上进行渲染。更好的是,在计时器上进行渲染和页面翻转,然后开始渲染下一帧。这不是什么大秘密,您可以在 Windows 中使用高分辨率计时器来实现。

一旦你得到这个工作,你会看到撕裂并决定等待垂直同步而不是一些任意的计时器。

于 2010-11-02T12:44:11.060 回答
1

在 DirectX 中,您可以简单地向 Present() 传递一个参数,该参数将使当前线程处于空闲状态,直到它返回,从而将 FPS 限制在刷新率的某个倍数。我用过它,可以说我的系统通过使用这个系统实现了大约 1% 的 CPU 使用率。您当前的代码基本上是错误的,因为 Sleep 是最不可靠的函数,简而言之,您几乎不应该将它与非零参数一起使用。

您可以尝试将 QueryPerformanceCounter 与 Sleep() 一起使用。

LARGE_INTEGER freq, begin, end;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&begin);
QueryPerformanceCounter(&end);
const unsigned __int64 waitinticks = static_cast<unsigned __int64>(static_cast<double>(1000)/60));
while(end.QuadPart - begin.QuadPart < waitinticks) {
    Sleep(0); // If someone else wants to run, let them.
    QueryPerformanceCounter(&end);
}

最好的办法仍然是使用等待计时器 - http://msdn.microsoft.com/en-us/library/ms682492(v=VS.85).aspx。这将使您的线程休眠,直到计时器发出警报。

于 2010-11-05T22:04:23.927 回答
1

如果有一些魔法让我错了,请原谅我,但据我所知,垂直同步和撕裂是齐头并进的。无论您的帧限制是多少,如果帧缓冲区在帧中间翻转,您仍然会撕裂。这就是 VSync 的用途。

至于帧限制代码,这里有一些进行生产者/消费者时间步进的技术:

http://gafferongames.com/game-physics/fix-your-timestep/

他的理由似乎集中在物理上,但它也完全适用于帧限制。

于 2010-11-05T22:09:35.923 回答