nice new timeSinceLastFrame filter, stable yet responsive. useful for interpolating stuff and of course for FPS.

FPS update code is a hack/unfinished, but it's not important right now.

This was SVN commit r619.
This commit is contained in:
janwas 2004-07-02 03:04:55 +00:00
parent fd6a4d6aed
commit b137e965f3

View File

@ -102,33 +102,57 @@ double timer_res()
}
// calculate fps (call once per frame)
// several smooth filters:
// - throw out single spikes / dips
// - average via small history buffer
// - update final value iff the difference (% or absolute) is too great,
// or if the change is consistent with the trend over the last few frames.
//
// => less fluctuation, but rapid tracking.
// calculate fps (call once per frame).
// algorithm: variable-gain IIR filter.
// less fluctuation, but rapid tracking.
// filter values are tuned for 100 FPS.
int fps = 0;
static char xbuf[1000000];
static char* xpos = xbuf;
static void dump(void)
{
FILE* f = fopen("test.csv", "w");
if(!f)
f = fopen("test.csv", "w");
fwrite(xbuf, xpos-xbuf, 1, f);
fclose(f);
}
void debug_out2(const char* fmt, ...)
{
ONCE(atexit(dump););
va_list ap;
va_start(ap, fmt);
int ret = vsprintf(xpos, fmt, ap);
if(ret > 0)
xpos += ret;
va_end(ap);
}
void calc_fps()
{
// history buffer - smooth out slight variations
RingBuf<float, 16> samples;
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 < 1e-3)
// bonus: if FPS > 1000, updates are slowed down a few frames.
if(dt < min_dt)
{
num_frames++;
return;
@ -136,40 +160,116 @@ void calc_fps()
// dt is big enough => we will update.
// calculate approximate current FPS (= 1 / elapsed time per frame).
float cur_fps = 30.0f; // start value => history converges faster
if(last_t != 0.0)
cur_fps = 1.0f / (float)dt * num_frames;
num_frames = 1; // reset for next time
last_t = t;
cur_fps = 1.0 / dt * num_frames;
num_frames = 1; // reset for next time
// calculate fps activity over 3 frames (used below to prevent fluctuation)
// -1: decreasing, +1: increasing, 0: neither or fluctuating
const float h1 = samples[-1]; // last frame's cur_fps
const float h2 = samples[-2]; // 2nd most recent frame's cur_fps
int trend = 0;
if(h2 > h1 && h1 > cur_fps) // decreasing
trend = -1;
else if(cur_fps < h1 && h1 < h2) // increasing
trend = 1;
// 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.
// ignore onetime skips in fps (probably page faults or similar)
static int ignored;
if(fabs(h1-cur_fps) > .05f*h1 && // > 5% difference
!ignored++) // was it first value we're discarding?
return; // yes: don't update fps_hist/fps
ignored = 0; // either value ok, or it wasn't a fluke - reset counter
//
// check cur_fps function for several characteristics that
// help decide if it's actually changing or just jittering.
//
// add new sample and average
samples.push_back(cur_fps);
const double sum_fps = std::accumulate(samples.begin(), samples.end(), 0.0);
const double avg_fps = sum_fps / (int)samples.size();
#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)
// update fps counter if update threshold is exceeded
const float d_avg = (float)(avg_fps-fps);
const float max_diff = fminf(5.f, 0.05f*fps);
if((trend > 0 && (avg_fps > fps || d_avg < -4.f)) || // going up, or large drop
(trend < 0 && (avg_fps < fps || d_avg > 4.f)) || // going down, or large raise
(fabs(d_avg) > max_diff)) // significant difference
// 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
static double la2, la1, la0;
la2 = la1; la1 = la0; la0 = avg_fps;
// if(ONE_SIDE(fps, la2, la1, la1 /*!*/))
// ;
const double d_avg = (avg_fps-fps);
const double max_diff = fminf(5.f, 0.05f*fps);
if( (fabs(d_avg) > max_diff)) // significant difference
{
fps = (int)avg_fps;
//debug_out("%d\n", fps);
}
//debug_out2("%f\t%f\t%f\t%f\n", cur_fps, avg_fps,(float)fps,(1.0-gain)*100.0);
}