This isn't new info, but it's interesting. I've been doing some research on timers, besides getting a cross-platform timing class made (easy), i also added some statistical tracking to it. Here is a live OpenGL graph that shows the per-frame time deltas in pink (the last 500), and a distribution graph in yellow. Interesting how they spike.
My test platform is using gettimeofday() on linux. I'm posting my code if you want to give it a spin. It requires OpenGL, but i'm sure you can easily adapt it to whatever you want. I haven't tested it at all in a Windows evironment. Feedback would be good, otherwise, you just scored yourself a free (but incomplete) Timer class. I make no guarantees about it's quality. This class is meant to be used with other code so it won't open it's own window or anything. All it does is time and draw.
Header:
#ifndef GAMETIMER_H
#define GAMETIMER_H
#include <stdlib.h>
#include <vector>
#include <deque>
#ifdef _WIN32
// Windows
#include <windows.h>
#else
// UNIX
#include <sys/time.h>
#endif
/**
USAGE:
Creating a timer will automatically begin the timing process.
Generally, you simply will want to call the GetTimeDelta() function
every trip through the game loop. This will update the timer's
internal data in addition to returning the delta. A delta is then
defined as the time between calls to GetTimeDelta(). To visualize,
call DrawGraph() every frame;
*/
class Timer {
public:
Timer();
~Timer() {};
/** Returns the time difference between now and the last time you punch out */
float GetTimeDelta();
/** */
void LogFPSToScreen( bool x = true ) { log_to_screen = x; }
/** */
void LogFPSToFile( bool x = true ) { log_to_file = x; }
/** */
void SetFramerateSamples( int x ) { framerate_samples = x; }
/** */
void DrawGraph();
private:
void Log();
void Track( float delta );
bool log_to_file;
bool log_to_screen;
int framerate_samples;
int FrameCount;
float FrameRate;
std::deque<float> most_recent;
float recent_max;
std::vector<long int> distrib;
bool distrib_inited;
long int distrib_max;
float distrib_unit;
#ifdef _WIN32
LARGE_INTEGER _tstart, _tend;
LARGE_INTEGER freq;
#else
struct timeval _tstart, _tend;
struct timezone tz;
#endif
};
#endif
Body:
#include "Timer.h"
#include <iostream>
#include <float.h>
#include <GL/gl.h>
static const unsigned int GRAPH_SIZE = 500;
Timer::Timer() {
log_to_file = false;
log_to_screen = true;
framerate_samples = 100;
FrameCount = 0;
FrameRate = 0;
// init the record keepers:
most_recent = std::deque<float>();
distrib = std::vector<long int>(GRAPH_SIZE,0);
distrib_inited = false;
distrib_max = 1;
recent_max = 0;
// Get the clock started:
#ifdef _WIN32
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&_tstart);
#else
gettimeofday(&_tstart, &tz);
#endif
}
float Timer::GetTimeDelta() {
static double fps_delta = 0;
static double fps_last = 0;
#ifdef _WIN32
QueryPerformanceCounter(&_tend);
float delta = ((double)_tend.QuadPart - (doublet)_tstart.QuadPart) / (double)freq.QuadPart;
// calculate FPS
if (++FrameCount >= framerate_samples) {
fps_delta = t2 - fps_last ;
FrameRate = double(framerate_samples) / fps_delta ;
FrameCount = 0;
if (fps_last != 0) { Log(); }
fps_last = t2;
}
_tstart = _tend;
Track( float(delta) );
return delta;
#else
gettimeofday(&_tend,&tz);
double t1, t2;
t1 = (double)_tstart.tv_sec + (double)_tstart.tv_usec / 1000000;
t2 = (double)_tend.tv_sec + (double)_tend.tv_usec / 1000000;
float delta = float(t2-t1);
// calculate FPS
if (++FrameCount >= framerate_samples) {
fps_delta = t2 - fps_last ;
FrameRate = double(framerate_samples) / fps_delta ;
FrameCount = 0;
if (fps_last != 0) { Log(); }
fps_last = t2;
}
_tstart = _tend;
Track( float(delta) );
return delta;
#endif
}
void Timer::Log() {
if (log_to_screen) {
std::cout << "FPS: " << FrameRate << std::endl;
}
if (log_to_file) {
// TODO
}
}
void Timer::Track( float delta ) {
static bool first_trip = true;
static unsigned int hits = 0;
if (first_trip) { first_trip = false; return; }
// log most recent
if ( most_recent.size() == GRAPH_SIZE) { most_recent.pop_front(); }
most_recent.push_back( delta );
if (recent_max == 0) { recent_max = delta; }
else if ( delta > recent_max ) {
if ( delta < recent_max * 4 ) { recent_max = delta; }
}
// set up the distribution graph if needed
if ( !distrib_inited && ++hits >= GRAPH_SIZE ) {
// first find the high and low points + average
float high = 0, low = FLT_MAX;
for ( std::deque<float>::iterator i = most_recent.begin(); i != most_recent.end(); i++ ) {
if ( *i < low ) { low = *i; }
if ( *i > high ) { high = *i; }
}
// setup distrib vector
//distrib_unit = (high - low) / float(GRAPH_SIZE);
distrib_unit = ( (1.0f/10.0f) - (1.0f/250.0f) ) / float(GRAPH_SIZE);
distrib_inited = true;
}
else if (distrib_inited) {
// log the distribution
unsigned long int index = int( delta / distrib_unit );
if (index >= GRAPH_SIZE) { index = GRAPH_SIZE - 1; }
else if (index < 0) { index = 0; }
distrib[index]++;
if ( distrib[index] > distrib_max ) { distrib_max = distrib[index]; }
}
}
void Timer::DrawGraph() {
glBindTexture( GL_TEXTURE_2D, 0 );
// draw base quad:
glColor4f(0,0,0,0.75);
glBegin(GL_QUADS);
glVertex2f( 0, 100 );
glVertex2f( GRAPH_SIZE, 100 );
glVertex2f( GRAPH_SIZE, 0 );
glVertex2f( 0, 0 );
glEnd();
// draw most recent graph
glBegin(GL_LINE_STRIP);
glColor4f(0.8, 0.6, 0.6, 0.5);
glVertex2f( GRAPH_SIZE, 50 ); // top of bar
glVertex2f( 0, 50 ); // top of bar
glColor4f(1.0, 0.8, 0.8, 1.0);
for ( unsigned int i = 0; i < most_recent.size(); i++ ) {
glVertex2f( i, (1.0 - (most_recent / recent_max)) * 50); // top of bar
}
glEnd();
// draw distrib graph
if (distrib_inited) {
glBegin(GL_LINES);
glColor4f(1.0, 1.0, 0.5, 1.0);
for ( unsigned int i = 0; i < distrib.size(); i++ ) {
glVertex2f( i, 100 ); // bottom of bar
glVertex2f( i, (1.0 - ((float)distrib / (float)distrib_max)) * 50 + 50); // top of bar
}
glEnd();
}
}