简体   繁体   中英

I need timer to show image buffer every 25fps (C++ win32)

I used SetTimer with 40ms and WM_TIMER message to draw an image every 40ms (25 frames per second) but the result is too slow.

I found out that WM_TIMER isn't accurate, I tried to use multimedia timer but the callback proceudure didn't work .

My question is: How can I draw my images with 25fps?

Thanks!

SetTimer can only be as accurate as your app allows it to be. It cannot deliver the WM_TIMER message until your main thread goes idle and re-enters the message loop. And if it takes more than 40 msec to paint the image then you'll inevitably fall behind. This is the exact same thing you see happening when you try to play a modern game on an old machine, the frame rate gets crappy.

And yes, timeSetEvent() cannot fix this problem. Its callback runs on a background thread. You cannot update the window in such a thread, windows are not thread-safe.

You can only achieve 25fps by making your painting code faster. DirectX helps with that.

You are indeed correct. WM_TIMER is grossly innacurate. If you try to do something similar with GetTickCount(), WaitForSingleObject(..., timeout) or Sleep(timeout) they won't work either, because they use a similar timing mechanism as WM_TIMER.

The simplest mechanism I can think of to accomplish your goal is to use the high-resolution performance counter . Note that there are some challenges here, such as potential problems with multiprocessor systems, wasted cycles due to spinning and even CPUs that don't implement hardware timers. But this is a starting point for you.

Here's how you use it:

First ask the system what the frequency of the performance counters are. Then loop and repeatedly getting the current time, and see how much time has elapsed. When 1/25th of a second has passed, do your thing. Example code:

#include <windows.h>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;

typedef unsigned __int64 u64;

string format_elapsed(u64 elapsed, u64 freq)
{
    stringstream ss;
    ss << static_cast<double>(elapsed)/static_cast<double>(freq) << " sec";
    return ss.str();
}

int main()
{

    LARGE_INTEGER li = {};
    QueryPerformanceFrequency(&li);
    u64 freq = static_cast<u64>(li.QuadPart); // clock ticks per second
    u64 period = 25;  // 25 fps
    u64 delay = freq/period;    // clock ticks between frame paints

    u64 start = 0, now = 0;
    QueryPerformanceCounter(&li);
    start = static_cast<u64>(li.QuadPart);

    for( ; ; )  // loop forever
    {
        QueryPerformanceCounter(&li);
        now = static_cast<u64>(li.QuadPart);
        if( now - start >= delay )
        {
            // time to paint
            cout << "Paint!  Elapsed = " << format_elapsed(now-start,freq) << "\n";
            // reset new start time & loop
            start = now;
        }
    }


}

WM_TIMER is a low priority message . That means that if you are processing a higher priority message (eg, WM_PAINT or WM_MOUSEMOVE ) via PeekMessage or GetMessage , it will not fire. You also can't use the multimedia timers directly since you should not draw to a window from a background thread.

What you can do with a multimedia timer callback is call SendMessage(...) with a custom message to redraw the window. Unfortunately, if you don't draw at least as frequently as that callback is called, you can accumulate messages in the message queue.

This may not be desired, so you would need to modify your callback to call SendMessage instead and use timeSetEvent with TIME_ONESHOT when queueing it up. Then, when SendMessage returns, call timeSetEvent with the delay set to 25 ms - time to process SendMessage . That way, you ensure that if the windows message thread gets behind, you don't flood the message queue with the same message.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM