2010-03-25 119 views
0

我正在创建一个依赖系统时间(因为它必须准确)的directx应用程序,我需要在后台运行每秒60次的代码行(在由boost :: thread创建的线程中)。这等于60 FPS(每秒帧数),但不取决于主应用帧速率。运行代码每秒只有60次

//................. 
void frameThread() 
{ 
    // I want to run codes inside this loop for *exactly* 60 times in a second. 
    // In other words, every 16.67 (1000/60) milliseconds 
    for(;;) 
    { 
     DoWork(); 
     //......... 
    } 
} 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 
{ 
    initialize(); 
    //.....stuffs 
    boost::thread framethread(frameThread); 
    //...... 
} 

有没有办法做到这一点?

任何形式的帮助,将不胜感激:)

+5

你知道,每秒运行60次并每1/60秒运行一次是有区别的:) – Axarydax 2010-03-25 09:05:08

+0

DoWork()执行是否应该在1秒内平均分配?可以执行60次,例如0.5秒内,然后是0.5秒怠速? – n0rd 2010-03-25 09:05:28

+0

它是连续的,但它应该每秒运行60次。 – 2010-03-25 09:21:01

回答

5

由于60 Hz是一种常见的显示器刷新率,听起来像您想启用V-Sync,而不是使用定时器和睡眠。

您提到您使用的是DirectX - 如果您使用DirectX 9,则在创建设备时指定D3DPRESENT_INTERVAL_ONE,并且应用程序将与显示器刷新率同步。通常这是60赫兹 - 但理论上它也可以是任何东西,例如50赫兹,85赫兹或120赫兹。不过,你可以得到一个非常漂亮的无损显示器 - 如果你不使用V-sync,你可能会在滚动时撕裂文物。

这引发了一个问题:如果帧速率变化如此之大,您如何以相同的速度让游戏进展?答案很简单 - 使用高分辨率计时器(如Windows上的QueryPerformanceCounter),并测量每帧之间的时间。那么你需要根据这个“增量时间”值来确定任何运动或运动。无论如何,即使采用固定的帧速率,这仍然是件好事 - 如果电脑速度较慢,并且玩家只能获得20fps,则可以防止您的游戏突然以慢动作运行。

这里的以每秒100个像素水平移动的对象的实例,而不管帧率的:

object.x += 100.0f * delta_time; // same speed no matter the framerate 

这IMO是这样做的一个更好的方法,尤其是考虑到它的自由撕裂-,framerate-独立的,并与用户的显示器同步,而不是你自己设定的任意帧率。

+0

谢谢。你能告诉我一个QueryPerformanceCounter的例子用法;简单的?我是那个API的新手。 – 2010-03-25 14:54:25

+0

只是谷歌的一些例子,它非常简单。 QueryPerformanceCounter为您提供高分辨率时间值,QueryPerformanceFrequency为您提供QueryPerformanceCounter每秒返回的单位。因此每调用一次QueryPerformanceCounter,获取值之间的差异,除以频率,就可以将您的高分辨率时间以秒为单位作为浮点数。 – AshleysBrain 2010-03-25 14:59:50

2

的DoWork(前获取系统时间),还要加上一秒钟的1/60,并将其存储以备后用。在DoWork()(必须运行少于1/60秒)之后,您必须睡眠,直到系统时间等于先前存储的时间。这应该够了吧。

+0

虽然要小心1/60秒的时间可能会少于系统时间的分辨率。当然,它接近它,所以你不会使用系统时间顺利分发。 – 2010-03-25 10:15:34

+0

的确如此。完全取决于用于检查时间和用于Sleep()调用的计时器的计时器的准确性。另一方面,60FPS回路中的轻微不准确性对用户来说应该不是很明显。 – 2010-03-25 10:37:10

1

使用waitable timer作为同步对象。

HANDLE hWaitTimer; 

void frameThread() 
{ 
    while (WaitForSingleObject(hWaitTimer, INFINITE) == WAIT_OBJECT_0) 
    { 
     // reactivate timer 
     LARGE_INTEGER due_time = {0}; 
     due_time.LowPart = 10000/60; // in 100 nanosecond intervals 
     ::SetWaitableTimer(hWaitTimer, &due_time, 0, NULL, NULL, FALSE); 

     DoWork(); 
     //......... 
    } 
} 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 
{ 
    hWaitTimer = ::CreateWaitableTimer(NULL, FALSE, NULL); 
    LARGE_INTEGER due_time = {0}; 
    due_time.LowPart = 10000/60; // in 100 nanosecond intervals 
    ::SetWaitableTimer(hWaitTimer, &due_time, 0, NULL, NULL, FALSE); 

    // create trhead etc. 


    // before exit 
    CloseHandle(hWaitTimer); 
} 
0

如果你真的需要精度高,使用特定于平台的多媒体计时器,看功能的timeSetEvent,http://msdn.microsoft.com/en-us/library/aa448195.aspx。使用TIME_PERIODIC和TIME_CALLBACK_FUNCTION标志,uResolution = 0。在任何情况下,DirectX都不可移植。

我不认为你可以使用跨平台Boost线程获得所需的精度。

0

如果你是100%,你将不会运行更长的1/60秒,那么解决方案将使用semaphore和单独的计时器,每1/60秒滴答。

在计时器线程你semaphore.V()和你的工作线程你semaphore.P()。

Windows有native semaphores

如果您的工作线程可能需要超过1/60,那么您应该通过某种“worker_lock”实例变量进行补偿。切勿锁定定时器线程,它必须尽可能快。同时提高定时器接收线程的优先级。

1

“准确”是什么意思? Windows不是一个RTOS,所以你不可能从中获得真正的准确性。你可以变得非常接近,但是你不能保证你的线程会在你想要的时候被安排。

无论如何,我会去等待定时器,线程池和RegisterWaitForSingleObject。

+0

意味着我需要代码在一秒内完全运行60次;不低于。 – 2010-03-25 09:19:48

+1

再说一遍,“完全”是什么意思?什么是要求的精度?是每秒运行正负10个MS的代码是否可以接受? 1毫秒? 100毫秒?规律性比精确性更重要吗?间隔是否需要花费时间来运行代码? – Stephane 2010-03-25 10:13:32

+1

@djzmo:这在Windows上是不可能的。假设某些高优先级的实体,如内核或设备驱动程序,运行速度较慢,CPU占用1/30秒或更长时间。那么你的程序在那段时间将不会运行,而且在第二秒结束时你可能不会“赶上”。你需要改变你的期望。 – 2010-03-25 10:18:34