Heya :)
I have a question concerning calculation of FramesPerSecond.
Looking around, I note that pretty much everyone does this by incrementing a counter once per Frame...
I do it a little differently, and want to know if anyone can see a problem with my method.It works like this:

I get fElapsedTime (in Seconds) as a QWORD on the fpu stack using highperf timer.
Now get its reciprocal (1/fElapsedTime) to calculate the fps.

Thinking about this a little with this example: if the elapsed time between Frames was 500ms (half a second), according to my formula , fps = 1 / 0.5, which equals 2 fps. Seems ok, right?

This method gives fps as a floating point value, more accurate than simply integer value.
Posted on 2004-11-17 21:47:29 by Homer
It works fine if you want the projected FPS from one frame, or don't mind averaging across several frames to get a human readable digital figure. I imagine it would produce a nice graph - really I imagine an tachometer with a needle bouncing... :)
Posted on 2004-11-17 22:00:31 by bitRAKE
Thats basically what it looks like :)

My current testbed project is getting (roughly !!) between 59.995 and 60.005 frames per second, which worried me at first, I thought I'd screwed up somewhere until I had a friend run it and report around half that framerate.
Posted on 2004-11-18 01:36:01 by Homer
Afternoon, EvilHomer2k.

The idea with regards to incrementing a counter every frame is so you can display the actual frames per second.
Every second you set the displayed value to the counted frames.
This stops the displayed fps from flickering all over the place.
Also:
If the fps isn't actually updated once per second then you're only really displaying the rendering time of the previous frame (i.e. it is no longer displaying frames per second).

So if you're going to use the 1/fElapsedTime method then you'll have to add up each value until 1 second has elapsed, then divide the summed value by the number of frames elapsed.
In other words...
You'll still have to keep an incrementing counter for each frame so that you'd know how many frames to divide the summed value by.

(Unless you only update the displayed value once per second using only the previous frames' elapsed time in the calculation). :P

Cheers,
Scronty
Posted on 2004-11-18 04:59:41 by Scronty
I do see your point about the flickering fps. Very annoying :)
The formula itself is ok though, and if I wanted fps rounded off to an integer value, I could just fistp it, right? I mean sure, that will be as much as 1fps error, but would you agree that the inc integer method suffers from the same error, since the "total" elapsed time will pretty much never fall on a full second, its always going to be one side or the other of 1.0 seconds, ie when OVER 1 second, the "remainder" elapsed time contributes to the NEXT second worth of frames - thus we are at least 1 fps in error.. agreed? And if I based my fps on the TOTAL ELAPSED TIME instead of the elapsed time between iterations, would that not make for extremely accurate results?
I'm quite tired and I hope I am not making a fool of myself here :P
Posted on 2004-11-18 06:36:26 by Homer
Would be interesting to have a counter for all the calculations?, I guess that would be more like Have a sistem for Real values and measure not only the PFS, but the calculation of the new world, the rendering, etc. Also trigered by events a new graph or measurement. In that way, measuring each part of your engine you will know where to put attention, pheraphs FPS are a static for the end user, but for the developer would be nice have a static of how fast is the IA calculations for simple and for complex events...

That would be like profiling ;) and making graphs with labels about the events trigered. You can measure one thing, or all the things at the same time and then make a graph of usage.


Also a side note, I am reading a book about quality, production, related to work not programming, but there is sayed that a graph only show to us numbers eg. they only say how many car accidents there are, pheraphs a more detailed can show to us car accident by drugs, simple accident, etc. But anyway they dosent say how we low the static about car accidents. What I mean is that we need to interpret the graph for get a real result and use for such a measurement tool oriented to programm parts.
Posted on 2004-11-18 07:48:49 by rea
I liked to use a method which took all frames into account but in which the most recent had the most influence :)

NewFrameRate = (OldFrameRate + CurrentFrameRate) / 2

CurrentFrameRate would be calculated say each frame with the method you propose. Only problem with that method is if the frames are actually taking a shorter time than your timer can measure. This happens alot with GetTickCount on Win 9x.
Posted on 2004-11-18 11:57:15 by Eóin
Afternoon, EvilHomer2k.

Having the FPS accurate to within 1 or 2 frames is fine. The FPS is supposed to be a guide to roughly how many frames per second the program is running at on the machine. It's not meant to be so accurate as to be used in actual updating of rendered objects :P .

This is the way I see it:
There are two main uses of time in a game.
(1) Time used for internal calculations for updating rendered objects (i.e. fElapsedTime).
(2) Time used for the updating of displayed data (i.e. an FPS counter).

For (1), you'd use the Performance Counter or timeGetTime to find the last elapsed time once and use this value for all rendered objects which move during that frame update.

For (2) it doesn't really matter which method you use for calculating the FPS. I'm only concerned with it being displayed in a sensible manner (i.e. the displayed FPS is only changed once per second so it doesn't flicker and the value is actually readable).

Also note that (2) would mean anything which is only for display on the screen and not for updating rendered objects.
i.e. this could include timings for text for a game console message to scroll at a certain speed. The text might be set to scroll upwards one line every 2 seconds.

Cheers,
Scronty
Posted on 2004-11-18 16:23:21 by Scronty
Sometimes its not good to get the elapsed time once for an entire frame worth of rendering - as an example of this, look at m$'s d3d particles demo - they create a blur effect by rendering the particles several times per frame via the time elapsed during the rending of the previous particle instance :)
I modified your Timer codebase a long time ago to have an eighth switch which gives the elapsed time but does not update the internal vars - this one you can call anytime while rending, but only after getting the once-per-frame Elapsed time (which I do immediately at the start of each frame). It returns the time that has elapsed since the start of the frame.

I don't plan on using the FPS value for anything at all, I was simply interested in why everyone wants to express it as an integer :) I think I might change it to show Frames over Time instead of fine FPS.
Posted on 2004-11-18 20:55:45 by Homer
OK, now to overengineer this :)
Let's view actual momentary FPS rate (i.e. 1/elapsed) as input, and the user display as output. For the function we're designing, we have the contradictory goals of rapid signal tracking (minimize output error WRT input) vs. stability (minimize second derivative).

Rapid tracking is important so that we notice changes to the scene immediately - e.g. impact of switching to a higher LOD level.
Of course we want the output to be legible, so it must not change too often.

As Scronty mentions, just sampling 1x per second takes care of legibility, but we can't see how the signal actually changes.
E?in's IIR filter (*) isn't bad, but we can modify it to track the signal more rapidly (similar to a sliding-window average, it will otherwise lag behind the actual input).

To calculate FPS, we first need the elapsed time. There's an annoying detail - some platforms only expose a ms timer, so we may need to wait a few frames until the elapsed time is "measurable" (say 4x timer resolution).
Now, what we will do is increase or decrease the gain on the IIR filter (making it track the signal more or less rapidly), depending on whether it looks like the input is actually changing, or just jittering.
Looking at some graphs, it turns out spikes ( \/ ) and jumps are mostly noise; when we see those in the history buffer, the gain is decreased. On the other hand, when output is consistently above or below the input, the function is actually changing, and we need to increase gain to keep up.

We then stuff this gain value (implemented as exponential bias) a.k.a. "sharpness" into a simple IIR filter: old = new*gain + old*(1.0-gain).
Finally, the user-visible FPS value is updated if it differs "enough" from this newly calculated FPS.

Here's the C code:


static double avg_fps = 30.0;
double cur_fps = avg_fps;

// get elapsed time [s] since last update
static double last_t;
const double t = get_time();
ONCE(last_t = t - 33e-3); // first call: 30 FPS
const double dt = t - last_t;

// (in case timer resolution is low): count frames until
// timer value has changed "enough".
static double min_dt;
ONCE(min_dt = timer_res() * 4.0);
// chosen to reduce error but still yield rapid updates.
static uint num_frames = 1;
if(dt < min_dt)
{
num_frames++;
return;
}

// dt is big enough => we will update.
// calculate approximate current FPS (= 1 / elapsed time per frame).
last_t = t;
cur_fps = 1.0 / dt * num_frames;
num_frames = 1; // reset for next time


// average and smooth cur_fps.
//
// filter design goals: steady output, but rapid signal tracking.
//
// implemented as a variable-gain IIR filter with knowledge of typical
// function characteristics. this is easier to stabilize than a PID
// scheme, since it is based on averaging actual function values,
// instead of trying to minimize output-vs-input error.
// there are some similarities, though: same_side ~= I, and
// bounced ~= D.

//
// check cur_fps function for several characteristics that
// help decide if it's actually changing or just jittering.
//

#define REL_ERR(correct, measured) (fabs((correct) - (measured)) / (correct))
#define SIGN_EQ(x0, x1, x2) ( ((x0) * (x1)) > 0.0 && ((x1) * (x2)) > 0.0 )
#define ONE_SIDE(x, x0, x1, x2) SIGN_EQ(x-x0, x-x1, x-x2)

// cur_fps history and changes over past few frames
static double h2, h1 = 30.0, h0 = 30.0;
h2 = h1; h1 = h0; h0 = cur_fps;
const double d21 = h1 - h2, d10 = h0 - h1, d20 = h0 - h2;
const double e20 = REL_ERR(h2, h0), e10 = REL_ERR(h1, h0);
const double e0 = REL_ERR(avg_fps, h0);

// indicators that the function is jittering
const bool bounced = d21 * d10 < 0.0 && e20 < 0.05 && e10 > 0.10;
// /\ or \/
const bool jumped = e10 > 0.30;
// large change (have seen semi-legitimate changes of 25%)
const bool close = e0 < 0.02;
// cur_fps - avg_fps is "small"

// "same-side" check for rapid tracking of the function.
// if the past few samples have been consistently above/below the average,
// the function is moving up/down and we need to catch up.
static int same_side;
// consecutive times the last 3 samples have been on the same side.
if(!ONE_SIDE(avg_fps, h0, h1, h2)) // not all on same side:
same_side = 0; // reset counter
// (only increase if not too close to average,
// so that this isn't triggered by jitter alone)
if(!close)
same_side++;


//
// determine filter gain, based on above characteristics.
//

static double gain; // sensitivity to changes in cur_fps ([0,1])
double bias = 0.0; // (unlimited) exponential change to gain

// ignore (gain -> 0) large jumps.
if(jumped)
bias -= 4.0;
// don't let a "bounce" affect things too much.
else if(bounced)
bias -= 1.0;
// otherwise, function is normal here.
else
{
// function is changing, we need to track it rapidly.
// note: check close again so we aren't too loose if the function
// comes closer to the average again (meaning it probably
// wasn't really changing).
if(same_side >= 2 && !close)
bias += min(same_side, 4);
}

// bias = 0: no change. > 0: increase (n-th root). < 0: decrease (^n)
double e = (bias > 0)? 1.0 / bias : -bias;
if(e == 0.0) e = 1.0;
gain = pow(0.08, e);
// default: fairly insensitive to changes (~= 16 sample average)


// IIR filter
static double old = 30.0;
old = cur_fps*gain + old*(1.0-gain);
avg_fps = old;

// update fps counter if it differs "enough"
// currently, that means off by more than 5 FPS or 5%.
const double difference = fabs(avg_fps-fps);
const double threshold = fminf(5.f, 0.05f*fps);
if(difference > threshold)
fps = (int)avg_fps;


Ever since I wasted a few hours on this, all subsequent projects have had a nice FPS display :)
I am very happy with the signal tracking, but the >update fps counter if it differs "enough"< part is kind of dirty. Can anyone think of a better way?


*: IIR = infinite impulse response. function output depends on all previous inputs (typically via "state" influencing output, calculated from previous input). If you send in a pulse (dirac function) input, the output will still "ring" (non-zero) later.
opposite is FIR (finite ..), where the output only depends on a sliding window of the last N inputs (no state); after the pulse has moved out of the window, output returns to 0.


phew! Thus ends our "going overboard with control theory" party. Any survivors? :)
Posted on 2004-11-20 09:07:26 by Jan Wassenberg
I'm impressed :)
I'll take the time to translate this for my own benchmarking purposes, and I'll add it to my current ogl engine dll project as an afterthought for end-users :)
Posted on 2004-11-21 03:27:12 by Homer
:)

hehe, just feed it into a compiler, that's a lot less work - it's not time-critical ;) But if you do translate it, could you send me a copy? I'd be interested to see how it looks.
Posted on 2004-11-21 15:22:51 by Jan Wassenberg
Here's an almost-completed translation which consumed five minutes of my life.. I'll finish it up next time I find five minutes to waste, unless someone beats me to it ;)



REL_ERR macro correct, measured
fld correct
fsub measured
fdiv correct
fabs
endm

SIGN_EQ macro x0,x1,x2
fld x0
fmul x1
fstp ftemp
.if ftemp & 80000000h == 0
fld x1
fmul x2
fstp ftemp
.if ftemp & 80000000 == 0
exitm <TRUE>
.endif
.endif
exitm <FALSE>
endm

ONE_SIDE macro x,x0,x1,x2
LOCAL xx,yy,zz
fld x
fsub x0
fstp xx
fld x
fsub x1
fstp yy
fld x
fsub x2
fstp zz
exitm <SIGN_EQ (xx,yy,zz)>
endm

.data
avg_fps REAL8 30.0f
cur_fps REAL8 30.0f
last_t REAL8 0.0f
t REAL8 0.0f
min_dt REAL8 0.20f ;minimum elapsed time is 200ms
h0 REAL8 30.0f
h1 REAL8 30.0f
h2 REAL8 30.0f
d10 REAL8 0.0f
d20 REAL8 0.0f
d21 REAL8 0.0f
e0 REAL8 0.0f
e10 REAL8 0.0f
e20 REAL8 0.0f
gain REAL8 0.0f ;// sensitivity to changes in cur_fps ([0,1])
bias REAL8 0.0f ;// (unlimited) exponential change to gain

num_frames dd 1
bBounced BOOL FALSE
bJumped BOOL FALSE
bClose BOOL FALSE
same_side dd 0

.code
; // get elapsed time [s] since last update
invoke Timer, TIMER_GETELAPSEDTIME
fstp dt

; // (in case timer resolution is low): count frames until
; // timer value has changed "enough".
; ONCE(min_dt = timer_res() * 4.0);
// chosen to reduce error but still yield rapid updates.
fld t
fcomp min_dt
__FJG @F if(dt < min_dt)
inc num_frames
ret
@@:

; // dt is big enough => we will update.
; // calculate approximate current FPS (= 1 / elapsed time per frame).
fld t
fstp last_t
fld1
fdiv t
fmul num_frames
fstp cur_fps
cur_fps = 1.0 / dt * num_frames;
mov num_frames , 1; // reset for next time

; // average and smooth cur_fps.
; //
; // filter design goals: steady output, but rapid signal tracking.
; //
; // implemented as a variable-gain IIR filter with knowledge of typical
; // function characteristics. this is easier to stabilize than a PID
; // scheme, since it is based on averaging actual function values,
; // instead of trying to minimize output-vs-input error.
; // there are some similarities, though: same_side ~= I, and
; // bounced ~= D.

; //
; // check cur_fps function for several characteristics that
; // help decide if it's actually changing or just jittering.
; //



; // cur_fps history and changes over past few frames

fld h1
fstp h2
fld h0
fstp h1
fld cur_fps
fstp h0 ; h2 = h1; h1 = h0; h0 = cur_fps;
fld h1 ;d21 = h1 - h2
fsub h2
fstp d21
fld h0
fsub h1
fstp d10 ;d10 = h0 - h1
fld h0
fsub h2
fstp d20 ; d20 = h0 - h2;
REL_ERR (h2, h0) ;e20 = REL_ERR(h2, h0)
fstp e20
REL_ERR (h1,h0) ;e10 = REL_ERR(h1, h0);
fstp e10
REL_ERR (avg_fps, h0) ;e0 = REL_ERR(avg_fps, h0);
fstp e0

; // indicators that the function is jittering
bBounced = d21 * d10 < 0.0 && e20 < 0.05 && e10 > 0.10;
fld d21
fmul d10
fstp ftemp
.if ftemp & 80000000 != 0 ;d21 * d10 < 0.0 (testing bit 31 for sign is valid for fp values)
fld e20 ;&& e20 < 0.05
fcomp r4_0_5
__FJGE @F
fld e10 ;&& e10 > 0.10
fcomp r4_0_1
__FJLE @F
mov bBounced, TRUE
.else
@@: mov bBounced, FALSE
.endif

; // /\ or \/
const bool jumped = e10 > 0.30;
mov bJumped, FALSE
fld e10
fcomp r4_0_3
__FJLE @F
mov bJumped, TRUE
@@:

; // large change (have seen semi-legitimate changes of 25%)
const bool close = e0 < 0.02;
mov bClose, FALSE
fld e0
fcomp r4_0_02
__FJGE @F
mov bClose, TRUE

; // cur_fps - avg_fps is "small"

; // "same-side" check for rapid tracking of the function.
; // if the past few samples have been consistently above/below the average,
; // the function is moving up/down and we need to catch up.
; // consecutive times the last 3 samples have been on the same side.

.if(!ONE_SIDE(avg_fps, h0, h1, h2)) ; // not all on same side:
mov same_side , 0 ; // reset counter
.endif
; // (only increase if not too close to average,
; // so that this isn't triggered by jitter alone)
.if !bClose
inc same_side
.endif

; //
; // determine filter gain, based on above characteristics.
; //

; // ignore (gain -> 0) large jumps.
.if bJumped
fld bias
fsub r4_4_0; bias -= 4.0;
fstp bias

; // don't let a "bounce" affect things too much.
.elseif bBounced
fld bias
fsub r4_1_0; bias -= 1.0;
fstp bias

;// otherwise, function is normal here.
.else
; // function is changing, we need to track it rapidly.
; // note: check close again so we aren't too loose if the function
; // comes closer to the average again (meaning it probably
; // wasn't really changing).
.if (same_side >= 2) && !bClose
fld same_side ; bias += min(same_side, 4);
fcomp r4_4_0
__FJL @F
fld r4_4_0
jmp past
@@: fild same_side
past: fadd bias
fstp bias
.endif

; // bias = 0: no change. > 0: increase (n-th root). < 0: decrease (^n)
double e = (bias > 0)? 1.0 / bias : -bias;
if(e == 0.0) e = 1.0;
gain = pow(0.08, e);
// default: fairly insensitive to changes (~= 16 sample average)


// IIR filter
static double old = 30.0;
old = cur_fps*gain + old*(1.0-gain);
avg_fps = old;

// update fps counter if it differs "enough"
// currently, that means off by more than 5 FPS or 5%.
const double difference = fabs(avg_fps-fps);
const double threshold = fminf(5.f, 0.05f*fps);
if(difference > threshold)
fps = (int)avg_fps;
Posted on 2004-11-22 14:46:26 by Homer