What's the Usual Way of Controlling Frame Rate

What's the usual way of controlling frame rate?

There are two "usual" ways of "controlling" framerate, and neither is quite that simple.

The first and more controlling of the two, and something that is usually optional, is VSync. This forces the video card to only push out a new frame when the monitor is done refreshing. Many monitors refresh at 60 Hz, so you tend to get 60 FPS.

This works quite well to cap framerate to monitor refresh rate, but when framerate drops below refresh, it's forced to the next multiple. Thus, as the framerate starts to drop a bit, you lose quite a bit of potential rendering time, because it's forced to 60, then 30, then 20, etc.

(a bit of info about vsync in DirectX and OpenGL)

The second method, commonly used (with vsync optionally added on) is not to limit framerate. Instead, adjust your code to handle differences. This is far more flexible in the long run and generally better coding, IMO, and much simpler than trying to force a particular FPS count.

Assuming you have a simple render loop, it starts out looking something like:

while ( gameloop )
{
float framedelta = ( timeNow - timeLast )
timeLast = timeNow;

for each ( GameObject object in World )
{
object->Animate(framedelta);
object->Move(speed * framedelta)
}

render();
}

You want to look at the time difference/time passed/delta, and work from there. Allow the framerate to scale based on hardware and settings (there are too many variations for you to predict or handle even half), and make your game work with that instead of controlling it. Much easier for you and more flexible and stable in practice.

Controlling frame rate

It looks to me that you likely have a units conversion error. This is typically caused by handling units of time outside of the chrono type system, and introducing explicit conversion factors. For example:

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

Can Work be modified to take duration<double> instead of double? This will help ensure that double is not reinterpreted as something other than seconds inside of Work. If for some reason it can not, you can at least reduce your exposure to error with:

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

The following looks very suspicious:

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

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

elapsedWorkTime clearly has units of seconds. minWorkTime appears to have units of milliseconds. The if throws away all units information. This should look like:

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

Or if you really want minWorkTime to represent milliseconds (which is not necessary):

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

I recommend the former because the introduction of 1000 is just another chance for an error to creep in.

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

Here again you are unnecessarily leaving the safety net of chrono and doing things manually. This should instead simply be:

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

Or if you want to be more verbose:

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

The use of .count() should be rare. It is currently necessary to use it when printing out durations. It can also be necessary to deal with legacy code which can not be made to work with chrono. If you find yourself using .count() more than that, step back and try to remove it. chrono is there to help prevent unit conversion errors, and when you exit the system via .count(), you subvert chrono's reason for existing.

Update

VS2013 has a bug such that it won't sleep on double-based durations. So as a workaround...

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

This is ugly, but still leaves all the conversion factors to chrono.

Thanks to Caesar for testing this workaround out for me.

Controlling fps with requestAnimationFrame?

How to throttle requestAnimationFrame to a specific frame rate

Demo throttling at 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/

This method works by testing the elapsed time since executing the last frame loop.

Your drawing code executes only when your specified FPS interval has elapsed.

The first part of the code sets some variables used to calculate elapsed time.

var stop = false;
var frameCount = 0;
var $results = $("#results");
var fps, fpsInterval, startTime, now, then, elapsed;

// initialize the timer variables and start the animation

function startAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}

And this code is the actual requestAnimationFrame loop which draws at your specified FPS.

// the animation loop calculates time elapsed since the last loop
// and only draws if your specified fps interval is achieved

function animate() {

// request another frame

requestAnimationFrame(animate);

// calc elapsed time since last loop

now = Date.now();
elapsed = now - then;

// if enough time has elapsed, draw the next frame

if (elapsed > fpsInterval) {

// Get ready for next frame by setting then=now, but also adjust for your
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
then = now - (elapsed % fpsInterval);

// Put your drawing code here

}
}

C++ How to make precise frame rate limit?

Yes, sleep is usually inaccurate. That is why you sleep for less than the actual time it takes to finish the frame. For example, if you need 5 more milliseconds to finish the frame, then sleep for 4 milliseconds. After the sleep, simply do a spin-lock for the rest of the frame. Something like

float TimeRemaining = NextFrameTime - GetCurrentTime();
Sleep(ConvertToMilliseconds(TimeRemaining) - 1);
while (GetCurrentTime() < NextFrameTime) {};

Edit: as stated in another answer, timeBeginPeriod() should be called to increase the accuracy of Sleep(). Also, from what I've read, Windows will automatically call timeEndPeriod() when your process exits if you don't before then.

How can I implement an accurate (but variable) FPS limit/cap in my OpenGL application?

I would suggest using sub-ms precision system timers (QueryPerformanceCounter, gettimeofday) to get timing data. These can help you profile performance in optimized release builds also.

What is the preferred way of getting the frame rate of a JavaFX application?

You can use an AnimationTimer.

The AnimationTimer's handle method is called once on each frame, and the value passed in is the current time in nanoseconds (a best approximation). So you can track how long since the previous frame.

Here's an implementation that tracks the times of the last 100 frames and computes the frame rate using them:

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class SimpleFrameRateMeter extends Application {

private final long[] frameTimes = new long[100];
private int frameTimeIndex = 0 ;
private boolean arrayFilled = false ;

@Override
public void start(Stage primaryStage) {

Label label = new Label();
AnimationTimer frameRateMeter = new AnimationTimer() {

@Override
public void handle(long now) {
long oldFrameTime = frameTimes[frameTimeIndex] ;
frameTimes[frameTimeIndex] = now ;
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
if (frameTimeIndex == 0) {
arrayFilled = true ;
}
if (arrayFilled) {
long elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
label.setText(String.format("Current frame rate: %.3f", frameRate));
}
}
};

frameRateMeter.start();

primaryStage.setScene(new Scene(new StackPane(label), 250, 150));
primaryStage.show();
}

public static void main(String[] args) {
launch(args);
}
}


Related Topics



Leave a reply



Submit