控制帧速率

时间:2014-08-04 10:48:19

标签: c++ multithreading c++11

我正在创建一堆需要在帧周期中工作的线程。我想控制一秒钟内完成的帧数。我简化了我的代码,所以我可以告诉你我写的是什么

// setup the frame timer
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();

while(running == true)
{
    // update timer
    start = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = start - end;
    double frameTime = elapsed_seconds.count();

    this->Work(frameTime);

    // update timer
    std::chrono::time_point<std::chrono::system_clock> afterWork = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsedWorkTime = afterWork - end ;

    const double minWorkTime = 1000 / this->timer.NumberOfFramePerSeconds;
    if(elapsedWorkTime.count() < minWorkTime)
    {
        double timeToSleep = minWorkTime - elapsedWorkTime.count();
        std::this_thread::sleep_for(std::chrono::milliseconds((int)timeToSleep));
    }

    // update fps
    end = start;
    timer.FrameCount += 1;
}

并非所有线程都有相同数量的工作,有些线程比其他线程更多,所以没有睡眠,我会得到围绕此Thread 1 : 150fps, Thread 2: 5000fps, Thread 3: 5000fps, Thread 4: 5000fps

的结果

我想要的是能够将每秒帧数设置为60,因此使用上面的代码我会将this->timer.NumberOfFramePerSeconds设置为60.我的问题是,当我这样做时,我最终会得到一个结果如Thread 1 : 30fps, Thread 2: 60fps, Thread 3: 60fps, Thread 4: 60fps

这表明我的代码有问题,因为当我注释掉睡眠时,线程1会愉快地做超过150帧,但是当睡眠存在时,它将在我试图达到的60以下工作。

我的代码/算法是否正确?

3 个答案:

答案 0 :(得分:4)

在我看来,您可能会遇到单位转换错误。这通常是由处理chrono类型系统之外的时间单位以及引入显式转换因子引起的。例如:

double frameTime = elapsed_seconds.count();
this->Work(frameTime);

可以修改Work以取duration<double>代替double吗?这有助于确保double不会被重新解释为Work内的其他内容。如果由于某种原因它不能,你至少可以通过以下方式减少错误:

this->Work(elapsed_seconds.count());

以下看起来非常可疑:

std::chrono::duration<double> elapsedWorkTime = afterWork - end ;

const double minWorkTime = 1000 / this->timer.NumberOfFramePerSeconds;
if(elapsedWorkTime.count() < minWorkTime)

elapsedWorkTime显然有几秒的单位。 minWorkTime 出现,单位为毫秒。 if会丢弃所有单位信息。这应该是:

std::chrono::duration<double> minWorkTime(1./this->timer.NumberOfFramePerSeconds);
if(elapsedWorkTime < minWorkTime)

或者,如果确实希望minWorkTime代表毫秒(这是不必要的):

std::chrono::duration<double, std::milli> minWorkTime(1000./this->timer.NumberOfFramePerSeconds);
if(elapsedWorkTime < minWorkTime)

我推荐前者,因为引入1000只是错误蔓延的另一个机会。

    double timeToSleep = minWorkTime - elapsedWorkTime.count();
    std::this_thread::sleep_for(std::chrono::milliseconds((int)timeToSleep));

在这里,您再次不必要地离开chrono的安全网并手动操作。这应该只是:

    std::this_thread::sleep_for(minWorkTime - elapsedWorkTime);

或者如果你想更加冗长:

    auto timeToSleep = minWorkTime - elapsedWorkTime;
    std::this_thread::sleep_for(timeToSleep);

使用.count()应该很少见。打印duration时,目前需要使用它。还可能需要处理无法与chrono一起使用的遗留代码。如果您发现自己使用.count()以上,请退后一步并尝试将其删除。 chrono有助于防止出现单位转换错误,当您通过.count()退出系统时,会破坏chrono现有原因。

<强>更新

VS2013有一个错误,它不会在基于双倍的持续时间内睡觉。所以作为一种解决方法......

std::this_thread::sleep_for(std::chrono::duration_cast
                <std::chrono::milliseconds>(minWorkTime - elapsedWorkTime));

这很难看,但仍然将所有转化因子留给chrono

感谢Caesar为我测试此解决方法。

答案 1 :(得分:2)

简单的答案是你不要。另请参阅this SO question

如果可能的话,您应该设置代码以处理当前帧速率,而不是控制容易出错并且不能很好地处理慢速线程的帧速率。

让线程尽可能多地完成工作。如果由于某种原因他们必须暂停,请使用消息队列并在它为空时休眠。

答案 2 :(得分:1)

睡眠通常不是很精确,因为时钟的分辨率可能很低。

例如,在Windows上,典型的计时器分辨率大约为15.6毫秒。如果您要求仅睡眠1毫秒,它将一直睡到下一个15.6毫秒的间隔,此时间隔至少为1毫秒。在平均上,这比你想要的长6.8毫秒。一旦时间过去,您无法保证处理器会立即安排您的线程。

所以我怀疑你的线程需要在短时间内休眠,但是它会睡得更长并且错过了下一帧。 (其他线程以60 fps运行的事实表明你还有其他速率限制器,如垂直同步。)

一种可能但效率低下的解决方案是在短于时钟分辨率的任何持续时间内进行忙等待而不是睡眠。例如,如果你必须延迟3.2 * clock_resolution,那么你可以睡3 * clock_resolutions,然后循环旋转直到实际时间到来。

另一种解决方案是让线程等待以所需速率发信号的同步原语。根据您可用的基元类型,当您尝试同步多个线程时,这可能会非常棘手。