Ali
Ali

Reputation: 58461

How can I ensure proper speed of an animation in Qt using OpenGL?

I have 200 frames to be displayed per second. The frames are very very simple, black and white, just a couple of lines. A timer is driving the animation. The goal is to play back the frames at approx. 200 fps.

Under Linux I set the timer to 5 ms and I let it display every frame (that is 200fps). Works just fine but it fails under Win 7.

Under Win 7 (the same machine) I had to set the timer to 20 ms and let it display every 4 frame (50 fps × 4 = 200). I found these magic numbers by trial and error.

What should I do to guarantee (within reasonable limits) that the animation will be played back at a proper speed on the user's machine?

For example, what if the user's machine can only do 30 fps or 60 fps?

Upvotes: 1

Views: 7203

Answers (4)

datenwolf
datenwolf

Reputation: 162164

I've already read, that you've data sampled at 200Hz, which you want to play back at natural speed. I.e. one second of sampled data shall be rendered over one second time.

First: Forget about using timers to coordinate your rendering, this is unlikely to work properly. Instead you should measure the time a full rendering cycle (including v-sync) takes and advance the animation time-counter by this. Now 200Hz is already some very good time resolution, so if the data is smooth enough, then there should be no need to interpolate at all. So something like this (Pseudocode):

objects[] # the objects, animated by the animation
animation[] # steps of the animation, sampled at 200Hz
ANIMATION_RATE = 1./200. # Of course this shouldn't be hardcoded,
                         # but loaded with the animation data

animationStep = 0

timeLastFrame = None
drawGL():
    timeNow = now() # time in seconds with (at least) ms-accuracy
    if timeLastFrame:
        stepTime = timeNow - timeLastFrame
    else:
        stepTime = 0

    animationStep = round(animationStep + stepTime * ANIMATION_RATE)
    drawObjects(objects, animation[animationStep])

    timeLastFrame = timeNow

It may be, that your rendering is much faster than the time between screen refreshs. In that case you may want to render some of the intermediate steps, too, to get some kind of motion blur effect (you can also use the animation data to obtain motion vectors, which can be used in a shader to create a vector blur effect), the render loop would then look like this:

drawGL():
    timeNow = now() # time in seconds with (at least) ms-accuracy
    if timeLastFrame:
        stepTime = timeNow - timeLastFrame
    else:
        stepTime = 0

    timeRenderStart = now()
    animationStep = round(animationStep + stepTime * ANIMATION_RATE)
    drawObjects(objects, animation[animationStep])

    glFinish() # don't call SwapBuffers
    timeRender = now() - timeRenderStart

    setup_GL_for_motion_blur()

    intermediates = floor(stepTime / timeRender) - 1 # subtract one to get some margin
    backstep = ANIMATION_RATE * (stepTime / intermediates)
    if intermediates > 0:
        for i in 0 to intermediates:
            drawObjects(objects, animation[animationStep - i * backstep])

    timeLastFrame = timeNow

Upvotes: 2

Thomi
Thomi

Reputation: 11808

There are probably two totally independant factors to consider here:

  1. How fast is the users machine? It could be that you are not achieving your target frame rate due to the fact that the machine is still processing the last frame by the time it is ready to start drawing the next frame.

  2. What is the resolution of the timers you are using? My impression (although I have no evidence to back this up) is that timers under Windows operating systems provide far poorer resolution than those under Linux. So you might be requesting a sleep of (for example) 5 mS, and getting a sleep of 15 mS instead.

Further testing should help you figure out which of these two scenarios is more pertinent to your situation.

If the problem is a lack of processing power, you can choose to display intermediate frames (as you are doing now), or degrade the visuals (lower quality, lower resolution, or anything else that might help speed thigns up).

If the problem is timer resolution, you can look at alternative timer APIs (Windows API provides two different timer functionc alls, each with different resolutions, perhaps you are using the wrong one), or try and compensate by asking for smaller time slices (as in Kdoto's suggestion). However, doing this may actually degrade performance, since you're now doing a lot more processing than you were before - you may notice your CPU usage spike under this method.

Edit:

As Drew Hall mentions in his answer, there's another whole site to this: The refresh rate you get in code may be very different to the actual refresh rate appearing on screen. However, that's output device dependent, and it sounds from your question like the issue is in code, rather than in output hardware.

Upvotes: 0

Drew Hall
Drew Hall

Reputation: 29055

The short answer is, you can't (in general).

For best aesthetics, most windowing systems have "vsync" on by default, meaning that screen redraws happen at the refresh rate of the monitor. In the old CRT days, you might be able to get 75-90 Hz with a high-end monitor, but with today's LCDs you're likely stuck at 60 fps.

That said, there are OpenGL extensions that can disable VSync (don't remember the extension name off hand) programmatically, and you can frequently disable it at the driver level. However, no matter what you do (barring custom hardware), you're not going to be able to display complete frames at 200 fps.

Now, it's not clear if you've got pre-rendered images that you need to display at 200 fps, or if you're rendering from scratch and hoping to achieve 200 fps. If it's the former, a good option might be to use a timer to determine which frame you should display (at each 60 Hz. update), and use that value to linearly interpolate between two of the pre-rendered frames. If it's the latter, I'd just use the timer to control motion (or whatever is dynamic in your scene) and render the appropriate scene given the time. Faster hardware or disabled VSYNC will give you more frames (hence smoother animation, modulo the tearing) in the same amount of time, etc. But the scene will unfold at the right pace either way.

Hope this is helpful. We might be able to give you better advice if you give a little more info on your application and where the 200 fps requirement originates.

Upvotes: 4

Olhovsky
Olhovsky

Reputation: 5549

One way is to sleep for 1ms at each iteration of your loop and check how much time has passed.

If more than the target amount of time has passed (for 200fps that is 1000/200 = 5ms), then draw a frame. Else, continue to the next iteration of the loop.

E.g. some pseudo-code:

target_time = 1000/200; // 200fps => 5ms target time.
timer = new timer(); // Define a timer by whatever method is permitted in your
                     // implementation.
while(){
    if(timer.elapsed_time < target_time){
       sleep(1);
       continue;
    }

    timer.reset(); // Reset your timer to begin counting again.

    do_your_draw_operations_here(); // Do some drawing.

}

This method has the advantage that if the user's machine is not capable of 200fps, you will still draw as fast as possible, and sleep will never be called.

Upvotes: 0

Related Questions