2006-04-12 01:59:08 +02:00
|
|
|
/**
|
|
|
|
* =========================================================================
|
|
|
|
* File : timer.cpp
|
|
|
|
* Project : 0 A.D.
|
|
|
|
* Description : platform-independent high resolution timer and
|
|
|
|
* : FPS measuring code.
|
|
|
|
*
|
|
|
|
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
|
|
|
|
* =========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2003-2005 Jan Wassenberg
|
|
|
|
*
|
|
|
|
* Redistribution and/or modification are also permitted under the
|
|
|
|
* terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation (version 2 or later, at your option).
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
*/
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
#include "precompiled.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-06-22 20:26:08 +02:00
|
|
|
#include <numeric>
|
|
|
|
#include <math.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
#include "lib.h"
|
2005-03-18 23:44:55 +01:00
|
|
|
#include "posix.h"
|
2004-06-01 19:34:12 +02:00
|
|
|
#include "timer.h"
|
2004-06-14 15:29:23 +02:00
|
|
|
#include "adts.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-06-22 20:26:08 +02:00
|
|
|
#include "lib/sysdep/cpu.h"
|
2006-01-29 19:23:47 +01:00
|
|
|
|
2004-06-22 18:05:44 +02:00
|
|
|
// rationale for wrapping gettimeofday and clock_gettime, instead of emulating
|
|
|
|
// them where not available: allows returning higher-resolution timer values
|
2005-10-19 03:53:38 +02:00
|
|
|
// than their us / ns interface, via double [seconds]. they're also not
|
2004-06-22 18:05:44 +02:00
|
|
|
// guaranteed to be monotonic.
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
double get_time()
|
|
|
|
{
|
|
|
|
double t;
|
|
|
|
|
2005-08-09 18:23:19 +02:00
|
|
|
#if HAVE_CLOCK_GETTIME
|
2005-01-23 18:48:32 +01:00
|
|
|
static struct timespec start = {0};
|
2004-03-03 00:56:51 +01:00
|
|
|
struct timespec ts;
|
|
|
|
|
|
|
|
if(!start.tv_sec)
|
2005-01-23 18:48:32 +01:00
|
|
|
(void)clock_gettime(CLOCK_REALTIME, &start);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-01-23 18:48:32 +01:00
|
|
|
(void)clock_gettime(CLOCK_REALTIME, &ts);
|
2004-03-03 00:56:51 +01:00
|
|
|
t = (ts.tv_sec - start.tv_sec) + (ts.tv_nsec - start.tv_nsec)*1e-9;
|
2005-08-09 18:23:19 +02:00
|
|
|
#elif HAVE_GETTIMEOFDAY
|
2004-03-03 00:56:51 +01:00
|
|
|
static struct timeval start;
|
|
|
|
struct timeval cur;
|
|
|
|
|
|
|
|
if(!start.tv_sec)
|
|
|
|
gettimeofday(&start, 0);
|
|
|
|
|
|
|
|
gettimeofday(&cur, 0);
|
2004-06-02 17:31:55 +02:00
|
|
|
t = (cur.tv_sec - start.tv_sec) + (cur.tv_usec - start.tv_usec)*1e-6;
|
2004-03-03 00:56:51 +01:00
|
|
|
#else
|
2006-07-26 16:04:52 +02:00
|
|
|
# error "get_time: add timer implementation for this platform!"
|
2004-03-03 00:56:51 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// make sure time is monotonic (never goes backwards)
|
2005-01-23 18:48:32 +01:00
|
|
|
static double t_last = 0.0;
|
2004-06-22 18:05:44 +02:00
|
|
|
if(t < t_last)
|
2004-03-03 00:56:51 +01:00
|
|
|
t = t_last;
|
2004-06-22 18:05:44 +02:00
|
|
|
t_last = t;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-10-18 01:35:16 +02:00
|
|
|
// return resolution (expressed in [s]) of the time source underlying
|
|
|
|
// get_time.
|
2004-03-03 00:56:51 +01:00
|
|
|
double timer_res()
|
|
|
|
{
|
2004-06-24 02:41:29 +02:00
|
|
|
// may take a while to determine, so cache it
|
2005-01-23 18:48:32 +01:00
|
|
|
static double cached_res = 0.0;
|
2004-06-24 02:41:29 +02:00
|
|
|
if(cached_res != 0.0)
|
|
|
|
return cached_res;
|
|
|
|
|
2005-01-23 18:48:32 +01:00
|
|
|
double res = 0.0;
|
2004-06-24 02:41:29 +02:00
|
|
|
|
2005-08-09 18:23:19 +02:00
|
|
|
#if HAVE_CLOCK_GETTIME
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-06-24 02:41:29 +02:00
|
|
|
struct timespec ts;
|
2005-01-23 18:48:32 +01:00
|
|
|
if(clock_getres(CLOCK_REALTIME, &ts) == 0)
|
|
|
|
res = ts.tv_nsec * 1e-9;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#else
|
|
|
|
|
2004-06-24 02:41:29 +02:00
|
|
|
const double t0 = get_time();
|
|
|
|
double t1, t2;
|
2004-06-22 18:05:44 +02:00
|
|
|
do t1 = get_time(); while(t1 == t0);
|
|
|
|
do t2 = get_time(); while(t2 == t1);
|
2004-06-24 02:41:29 +02:00
|
|
|
res = t2-t1;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
#endif
|
2004-06-24 02:41:29 +02:00
|
|
|
|
|
|
|
cached_res = res;
|
|
|
|
return res;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-07-02 05:04:55 +02:00
|
|
|
// calculate fps (call once per frame).
|
|
|
|
// algorithm: variable-gain IIR filter.
|
|
|
|
// less fluctuation, but rapid tracking.
|
2004-03-03 00:56:51 +01:00
|
|
|
// filter values are tuned for 100 FPS.
|
|
|
|
|
2004-08-12 18:06:47 +02:00
|
|
|
int fps;
|
2005-10-18 01:35:16 +02:00
|
|
|
float spf;
|
2004-07-02 05:04:55 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
void calc_fps()
|
|
|
|
{
|
2005-01-07 01:52:05 +01:00
|
|
|
static double avg_fps = 60.0;
|
2004-07-02 05:04:55 +02:00
|
|
|
double cur_fps = avg_fps;
|
2004-06-14 15:29:23 +02:00
|
|
|
|
|
|
|
// get elapsed time [s] since last update
|
2004-03-03 00:56:51 +01:00
|
|
|
static double last_t;
|
2004-06-14 15:29:23 +02:00
|
|
|
const double t = get_time();
|
2005-01-07 01:52:05 +01:00
|
|
|
ONCE(last_t = t - 1.0/60.0); // first call: 60 FPS
|
2004-06-14 15:29:23 +02:00
|
|
|
const double dt = t - last_t;
|
|
|
|
|
|
|
|
// (in case timer resolution is low): count frames until
|
|
|
|
// timer value has changed "enough".
|
2004-07-02 05:04:55 +02:00
|
|
|
static double min_dt;
|
|
|
|
ONCE(min_dt = timer_res() * 4.0);
|
|
|
|
// chosen to reduce error but still yield rapid updates.
|
2004-06-14 15:29:23 +02:00
|
|
|
static uint num_frames = 1;
|
2004-07-02 05:04:55 +02:00
|
|
|
if(dt < min_dt)
|
2004-06-14 15:29:23 +02:00
|
|
|
{
|
|
|
|
num_frames++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// dt is big enough => we will update.
|
|
|
|
// calculate approximate current FPS (= 1 / elapsed time per frame).
|
2004-03-03 00:56:51 +01:00
|
|
|
last_t = t;
|
2005-01-23 18:48:32 +01:00
|
|
|
cur_fps = (1.0 / dt) * num_frames;
|
2004-07-02 05:04:55 +02:00
|
|
|
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;
|
2005-01-23 18:48:32 +01:00
|
|
|
const double d21 = h1 - h2, d10 = h0 - h1;
|
2004-07-02 05:04:55 +02:00
|
|
|
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%)
|
2005-01-23 18:48:32 +01:00
|
|
|
const bool is_close = e0 < 0.02;
|
2004-07-02 05:04:55 +02:00
|
|
|
// 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)
|
2005-01-23 18:48:32 +01:00
|
|
|
if(!is_close)
|
2004-07-02 05:04:55 +02:00
|
|
|
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).
|
2005-01-23 18:48:32 +01:00
|
|
|
if(same_side >= 2 && !is_close)
|
|
|
|
bias += MIN(same_side, 4);
|
2004-07-02 05:04:55 +02:00
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2004-07-02 05:04:55 +02:00
|
|
|
// 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;
|
|
|
|
|
2005-10-18 01:35:16 +02:00
|
|
|
spf = 1.0 / avg_fps;
|
|
|
|
|
2004-08-12 18:06:47 +02:00
|
|
|
// 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)
|
2005-01-07 01:52:05 +01:00
|
|
|
fps = (int)(avg_fps + 0.99);
|
|
|
|
// C float -> int rounds down; we want to round up to
|
|
|
|
// hit vsync-locked framerates exactly.
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
2005-10-10 19:33:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// cumulative timer API, useful for profiling.
|
|
|
|
// this supplements in-game profiling by providing low-overhead,
|
|
|
|
// high resolution time accounting.
|
|
|
|
|
2005-10-18 01:35:16 +02:00
|
|
|
// intrusive linked-list of all clients. a fixed-size limit would be
|
|
|
|
// acceptable (since timers are added manually), but the list is easy
|
|
|
|
// to implement and only has the drawback of exposing TimerClient to users.
|
|
|
|
//
|
|
|
|
// do not use std::list et al. for this! we must be callable at any time,
|
|
|
|
// especially before NLSO ctors run or before heap init.
|
2005-10-10 19:33:34 +02:00
|
|
|
static uint num_clients;
|
2005-10-17 01:16:08 +02:00
|
|
|
static TimerClient* clients;
|
2005-10-10 19:33:34 +02:00
|
|
|
|
|
|
|
|
2005-10-18 01:35:16 +02:00
|
|
|
// make the given TimerClient (usually instantiated as static data)
|
|
|
|
// ready for use. returns its address for TIMER_ADD_CLIENT's convenience.
|
|
|
|
// this client's total (added to by timer_bill_client) will be
|
|
|
|
// displayed by timer_display_client_totals.
|
2005-10-10 19:33:34 +02:00
|
|
|
// notes:
|
|
|
|
// - may be called at any time;
|
2005-10-17 01:16:08 +02:00
|
|
|
// - always succeeds (there's no fixed limit);
|
2005-10-10 19:33:34 +02:00
|
|
|
// - free() is not needed nor possible.
|
2005-10-17 01:16:08 +02:00
|
|
|
// - description must remain valid until exit; a string literal is safest.
|
|
|
|
TimerClient* timer_add_client(TimerClient* tc, const char* description)
|
2005-10-10 19:33:34 +02:00
|
|
|
{
|
2005-10-17 01:16:08 +02:00
|
|
|
tc->sum = 0.0;
|
|
|
|
tc->description = description;
|
|
|
|
|
|
|
|
// insert at front of list
|
|
|
|
tc->next = clients;
|
|
|
|
clients = tc;
|
|
|
|
num_clients++;
|
|
|
|
|
2005-10-10 19:33:34 +02:00
|
|
|
return tc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-12-14 00:17:50 +01:00
|
|
|
// add <dt> to the client's total.
|
|
|
|
void timer_bill_client(TimerClient* tc, TimerUnit dt)
|
2005-10-10 19:33:34 +02:00
|
|
|
{
|
|
|
|
tc->sum += dt;
|
2005-10-19 22:26:53 +02:00
|
|
|
tc->num_calls++;
|
2005-10-10 19:33:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// display all clients' totals; does not reset them.
|
|
|
|
// typically called at exit.
|
|
|
|
void timer_display_client_totals()
|
|
|
|
{
|
|
|
|
debug_printf("TIMER TOTALS (%d clients)\n", num_clients);
|
|
|
|
debug_printf("-----------------------------------------------------\n");
|
2005-10-17 01:16:08 +02:00
|
|
|
|
|
|
|
while(clients)
|
2005-10-10 19:33:34 +02:00
|
|
|
{
|
2005-10-17 01:16:08 +02:00
|
|
|
// (make sure list and count are consistent)
|
|
|
|
debug_assert(num_clients != 0);
|
|
|
|
TimerClient* tc = clients;
|
|
|
|
clients = tc->next;
|
|
|
|
num_clients--;
|
|
|
|
|
2005-12-14 00:17:50 +01:00
|
|
|
// convert raw ticks into seconds, if necessary
|
|
|
|
double sum;
|
|
|
|
#if TIMER_USE_RAW_TICKS
|
|
|
|
# if CPU_IA32
|
|
|
|
sum = tc->sum / cpu_freq;
|
|
|
|
# else
|
|
|
|
# error "port"
|
|
|
|
# endif
|
|
|
|
#else
|
|
|
|
sum = tc->sum;
|
|
|
|
#endif
|
|
|
|
|
2005-10-10 19:33:34 +02:00
|
|
|
// determine scale factor for pretty display
|
|
|
|
double scale = 1e6;
|
|
|
|
const char* unit = "us";
|
|
|
|
if(sum > 1.0)
|
|
|
|
scale = 1, unit = "s";
|
|
|
|
else if(sum > 1e-3)
|
|
|
|
scale = 1e3, unit = "ms";
|
|
|
|
|
2005-10-19 22:26:53 +02:00
|
|
|
debug_printf(" %s: %g %s (%dx)\n", tc->description, sum*scale, unit, tc->num_calls);
|
2005-10-10 19:33:34 +02:00
|
|
|
}
|
2005-10-17 01:16:08 +02:00
|
|
|
|
2005-10-10 19:33:34 +02:00
|
|
|
debug_printf("-----------------------------------------------------\n");
|
|
|
|
}
|