//==============================================================================
// Programme ... Affichage et Limitation du Frame Rate dans une application OpenGL
// Date ........ 11-13 septembre 2004 (MAJ 9 septembre 2005)
// Auteur ...... BeLZeL
//==============================================================================
// TOUCHE :
// '+' -> Je veux + de FPS
// '-' -> Je veux - de FPS
// ENTER -> Je veux touplin de cubes
// SPACE -> Je veux activer/stopper la MAJ de la console
//==============================================================================
// A connaitre !
// _ QueryPerformanceFrequency (fonction) : Retourne le nombre de cycles par seconde
// _ QueryPerformanceCounter (fonction) : Retourne le nombre de cycles depuis le démarrage de Windows
// _ LONGLONG (type) : je crois que c'est l'équivalent d'un __int64 (à voir)
// _ LARGE_INTEGER (type) : structure spéciale équivalente d'un LONGLONG
// _ FPS (frames per second) : nombre d'images calculées ET affichées par seconde
//==============================================================================
// Pourquoi faire ?
// _ Il est souvent inutile de gaspiller du temps machine pour afficher jusqu'à
// 3000 FPS avec des cartes graphiques dernier cri (aaaaaahhh !).
// L'oeil n'en percoit qu'une centaine au maximum.
// Ce programme calcule le nombre de FPS. Il limite le nombre de FPS selon votre choix.
// Il déplace les objets selon la méthode du TimeStep (déplacement en fonction du
// temps).
//==============================================================================
// Comment faire ?
// _ Le principal soucis pour calculer le nombre d'images par seconde est de réussir
// à trouver des fonctions de timing très précises. Les fonctions classiques
// sont trop imprécises (de l'ordre de 1 à 10 ms). C'est une précision insuffisante
// lorsqu'on désire afficher plus de 100 FPS (ca nous fait une image toutes les
// 10ms ou moins). Les seules fonctions intéressantes sont QueryPerformanceFrequency
// et QueryPerformanceCounter (qui existent bien sur tous les processeurs, quoi
// qu'en dise la MSDN, qui doit dater un peu). Leur seul inconvénient est de ne
// pas donner directement le temps, il faut au préalable diviser le nombre de cycle
// par la fréquence du nombre de cycles par seconde. Il y a donc un coût en temps
// supplémentaire pour les divisions et également un coût en mémoire pour stocker
// les résultats en seconde dans des variables de type double.
// _ Le nombre de cycles est un résultat beaucoup plus précis que le millième de seconde.
// Un cycle correspond environ (sur mon PC, Barton 3200+) à 270 nano secondes.
// _ Pour calculer le nombre de frames par seconde (FPS), nous allons comptabiliser
// toutes les frames affichées (temps.nbFrames). Ensuite, toutes les secondes,
// on affiche le résultat et on remet le compteur à 0. Le résultat est assez précis.
// _ Pour limiter le nombre de FPS, il faut 2 variables. Il faut comptabiliser
// le nombre de cycles maintenant (temps.cSuiv) et le nombre de cycles pour la
// prochaine frame (temps.cSuiv). Dès que temps.cNow > temps.cSuiv, on
// dessine une nouvelle image et on augmente temps.cSuiv. Si on veut beaucoup de FPS,
// on ajoutera un peu de cycles à temps.cSuiv, ou inversement.
// _ Pour animer un objet, on n'incrémentera pas son déplacement de manière statique
// à chaque image (pos++). On utilisera le temps qu'il s'est écoulé entre deux frames
// (pos += temps.cSuiv - temps.cPrec) pour savoir où l'objet sera placé.
// _ Concernant l'affichage OpenGL, la fonction glutSwapBuffers() permet
// un affichage en double buffering. Il faut uniquement l'utiliser lorsqu'on est
// sur le point d'afficher une nouvelle image (temps.cNow > temps.cSuiv).
// La fonction glutPostRedisplay() permet de redemander l'affichage d'une nouvelle
// image, il faut toujours utiliser cette fonction à la fin de l'affichage, que
// la frame précédente soit affichée ou non.
//==============================================================================
//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------
#include <windows.h> // Header pour les Applications Windows
#include <stdio.h> // Pour les prints
#include <GL/glut.h> // Header OpenGL Utility Toolkit (GLUT)
// A changer selon votre carte 3D (j'ai une ATI Radeon 9600XT @ GPU533/RAM340)
// J'ai 141 FPS avec 5000 cubes, sinon j'ai 1970 FPS avec 1 cube
#define NB_CUBES 5000
//------------------------------------------------------------------------------
// Structures
//------------------------------------------------------------------------------
// Gestion du temps : toutes les variables ne sont pas indispensables
struct TEMPS
{
// Par rapport au démarrage de Windows
LONGLONG cBase; // Nombre de cycles depuis le démarrage de Windows, trouvé au démarrage de l'application
LONGLONG cPrec; // Nombre de cycles depuis le démarrage de Windows, trouvé lors du dernier rendu
LONGLONG cNow; // Nombre de cycles depuis le démarrage de Windows, trouvé actuellement
LONGLONG cSuiv; // Nombre de cycles depuis le démarrage de Windows, qu'on doit atteindre pour le prochain rendu
// Par rapport au démarrage de l'application
LONGLONG cBegin; // Nombre de cycles écoulés uniquement depuis le démarrage de l'application
LONGLONG allFrames; // Nombre de frames depuis le démarrage de l'application
LONGLONG allFramesPrec; // Nombre de frames depuis le démarrage de l'application
LONGLONG cUneSeconde; // On attend une seconde pour comptabiliser le nombre de frames
long int ns; // Nombre de nano secondes depuis le démarrage de l'application
// Par rapport aux conversions temps <=> cycle
LONGLONG frequence; // Nombre de cycles par seconde (de l'ordre de 3 millions avec un Barton 3200+)
double tpsPerC; // Temps en seconde entre deux cycles
// Par rapport aux frames
LONGLONG cPerFrame; // Nombre de cycles qui doivent s'écouler entre deux frames (pour la limitation)
LONGLONG cEcoule; // Nombre de cycles écoulés entre deux frames (pour le TimeStep)
double ecoule; // Temps en secondes entre deux frames (pour le TimeStep)
LONGLONG nbFrames; // Nombre de frames affichées (remis à 0 toutes les secondes)
double fps; // Nombre de frames par seconde (mis à jour toutes les secondes via nbFrames)
};
struct TEMPS temps;
//==============================================================================
// Variables Globales
//==============================================================================
// Tableau qui indiquera à combien il faut limiter le nombre d'images par seconde (en nombres de FPS)
int idFPS = 5;
#define MAX_ID_FPS 12
int tabFPS[MAX_ID_FPS+1] = { 1, 5, 10, 15, 20, 25, 30, 50, 100, 250, 850, 1200, 20000 };
int MAX_FPS = 25;
// Affiche les informations en temps réel dans la console DOS
int showInfos = 1;
// Affiche ou non plusieurs cubes
int ramage = 0;
//------------------------------------------------------------------------------
// Fonction de redimensionnement
//------------------------------------------------------------------------------
void Reshape ( int largeur, int hauteur )
{
glViewport ( 0, 0, largeur, hauteur );
glMatrixMode ( GL_PROJECTION );
glLoadIdentity ( );
gluPerspective ( 90, (float)largeur / (float) hauteur, 1, 100 );
}
//------------------------------------------------------------------------------
// Fonction d'initialisation
//------------------------------------------------------------------------------
void InitGL()
{
glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f ); // Arrière Plan Noir
glClearDepth ( 1.0f ); // La profondeur du buffer
glDepthFunc( GL_LEQUAL );
glEnable ( GL_DEPTH_TEST );
glDisable ( GL_TEXTURE_2D );
glEnable ( GL_COLOR );
//*** INITIALISATION DES VARIABLES *****************************************
QueryPerformanceFrequency ( (LARGE_INTEGER *) &temps.frequence );
QueryPerformanceCounter ( (LARGE_INTEGER *) &temps.cBase );
temps.cPrec = temps.cSuiv = temps.cBase;
temps.cPerFrame = temps.frequence / MAX_FPS;
temps.tpsPerC = 1.0 / (double)temps.frequence;
temps.nbFrames = 0;
temps.allFrames = 0;
temps.cUneSeconde = temps.cBase + temps.frequence;
srand ( temps.cBase );
//**************************************************************************
}
//==============================================================================
// Affiche les informations en temps réel dans la console DOS
//==============================================================================
void ShowInfos ( )
{
// system ( "CLS" );
printf ( "-------------------------------------------------------------------------------\r\n" );
printf ( "TEMPS. VALEUR DESCRIPTION\r\n" );
printf ( "frequence %010lu Nb cycles par seconde\r\n", temps.frequence );
printf ( "cBase %010lu Nb cycles trouve au demarrage de l'application\r\n", temps.cBase );
printf ( "ns %010ld Nb nano secondes depuis le demarrage de l'application\r\n", temps.ns );
printf ( "cPrec %010lu Nb cycles trouve lors du dernier rendu\r\n", temps.cPrec );
printf ( "cNow %010lu Nb cycles trouve actuellement\r\n", temps.cNow );
printf ( "cSuiv %010lu Nb cycles a atteindre pour le prochain rendu\r\n", temps.cSuiv );
printf ( "cBegin %010lu Nb cycles ecoules depuis le demarrage de l'application\r\n",temps.cBegin );
printf ( "allFrames %010lu Nb frames depuis le demarrage de l'application\r\n", temps.allFrames );
printf ( "cEcoule %010lu Nb cycles ecoules depuis le dernier rendu\r\n", temps.cEcoule );
printf ( "ecoule %010.5lf Temps en milli-secondes entre deux frames\r\n", temps.ecoule*1000 );
printf ( "cPerFrame %010lu Nb cycles entre l'affichage de deux frames\r\n", temps.cPerFrame );
printf ( "tpsPerC %010.6lf Temps en nano seconde entre deux cycles\n", temps.tpsPerC*1000000000 );
printf ( "nbFrames %010lu Nb frames (Valeur qui change a chq nouvelle frame)\r\n", temps.nbFrames );
printf ( "fps %010.0lf Nb FPS (Valeur qui change toutes les secondes)\r\n", temps.fps );
printf ( "-------------------------------------------------------------------------------\r\n" );
printf ( "Appuyez sur Entree pour stresser l'affichage 3D (avec %d cubes)\r\n", NB_CUBES );
printf ( "Appuyez sur Espace pour stopper l'affichage de la console\r\n" );
printf ( "-------------------------------------------------------------------------------\r\n" );
}
//==============================================================================
// Modifie le titre de la fenêtre
//==============================================================================
void Titre ( )
{
char title[1024];
if ( showInfos ) ShowInfos ( );
// Information : wsprintf n'aime pas les floats (%f)
wsprintf ( title, "%02d:%02d (%d frames) (%d FPS) (MAX:%d FPS)",
(int)temps.ns/1000/1000/60, // Minutes
(int)temps.ns/1000/1000%60, // Secondes
(int)temps.allFrames, // Nombre de frames (depuis le dernier changement de framerate)
(int)(temps.fps+0.5), // Nombre de FPS précis
tabFPS[idFPS] ); // Nombre maximal de FPS
glutSetWindowTitle ( title );
}
//------------------------------------------------------------------------------
// Gestion des touches standard
//------------------------------------------------------------------------------
void GestionClavier ( unsigned char key, int x, int y )
{
switch ( key )
{
//=== On réduit le framerate ===========================================
case '-' :
MAX_FPS = ( idFPS == 0 ) ? tabFPS[0] : tabFPS[--idFPS];
temps.cPerFrame = temps.frequence / MAX_FPS;
temps.cPrec = temps.cSuiv = temps.cNow;
break;
//======================================================================
//=== On augmente le framerate =========================================
case '+' :
MAX_FPS = ( idFPS == MAX_ID_FPS ) ? tabFPS[MAX_ID_FPS] : tabFPS[++idFPS];
temps.cPerFrame = temps.frequence / MAX_FPS;
temps.cPrec = temps.cSuiv = temps.cNow;
break;
//======================================================================
//=== On augmente la vitesse de changement du titre ====================
case ' ' :
showInfos = 1 - showInfos;
break;
//======================================================================
//=== On change le nombre de cube à l'écran ============================
case 13 :
ramage = 1 - ramage;
break;
//======================================================================
// Quitter avec Echap
case 27 :
exit ( 0 );
break;
}
}
//------------------------------------------------------------------------------
// Affiche une sphè ... heu non un cube ;)
//------------------------------------------------------------------------------
void Cube ( )
{
// Notre Cube
glBegin(GL_QUADS);
// derrière
glColor3f ( 0, 0, 0 ); glVertex3f ( -5, 5, -5 );
glColor3f ( 1, 0, 0 ); glVertex3f ( 5, 5, -5 );
glColor3f ( 1, 1, 0 ); glVertex3f ( 5, -5, -5 );
glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
// devant
glColor3f ( 0, 0, 1 ); glVertex3f ( -5, 5, 5 );
glColor3f ( 1, 0, 1 ); glVertex3f ( 5, 5, 5 );
glColor3f ( 1, 1, 1 ); glVertex3f ( 5, -5, 5 );
glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5, 5 );
// gauche
glColor3f ( 0, 0, 0 ); glVertex3f ( -5, 5, -5 );
glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5, 5 );
glColor3f ( 0, 0, 1 ); glVertex3f ( -5, 5, 5 );
// droite
glColor3f ( 1, 0, 0 ); glVertex3f ( 5, 5, -5 );
glColor3f ( 1, 1, 0 ); glVertex3f ( 5, -5, -5 );
glColor3f ( 1, 1, 1 ); glVertex3f ( 5, -5, 5 );
glColor3f ( 1, 0, 1 ); glVertex3f ( 5, 5, 5 );
// haut
glColor3f ( 0, 0, 0 ); glVertex3f ( -5, 5, -5 );
glColor3f ( 1, 0, 0 ); glVertex3f ( 5, 5, -5 );
glColor3f ( 1, 0, 1 ); glVertex3f ( 5, 5, 5 );
glColor3f ( 0, 0, 1 ); glVertex3f ( -5, 5, 5 );
// bas
glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
glColor3f ( 1, 1, 0 ); glVertex3f ( 5, -5, -5 );
glColor3f ( 1, 1, 1 ); glVertex3f ( 5, -5, 5 );
glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5, 5 );
glEnd ( );
glColor3f ( 1, 1, 1 );
}
//------------------------------------------------------------------------------
// Fonction de dessin
//------------------------------------------------------------------------------
void Draw ( )
{
static double a = 0;
QueryPerformanceCounter ( (LARGE_INTEGER *) &temps.cNow );
// Calcul des FPS
if ( temps.cNow >= temps.cUneSeconde )
{
temps.fps = (double)temps.nbFrames;
temps.cUneSeconde += temps.frequence;
Titre ( );
temps.nbFrames = 0;
}
// Doit-on afficher une image ?
if ( temps.cNow >= temps.cSuiv )
{
//=== Mise à jour du temps =============================================
temps.allFrames++;
temps.nbFrames++;
temps.cBegin = temps.cNow - temps.cBase;
temps.ns = (long)(temps.cBegin * temps.tpsPerC * 1000000);
temps.cEcoule = temps.cNow - temps.cPrec;
temps.cPrec = temps.cNow;
temps.cSuiv += (LONGLONG)(temps.cPerFrame);
temps.ecoule = temps.cEcoule * temps.tpsPerC;
//======================================================================
// Reset de l'affichage
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode ( GL_MODELVIEW );
glLoadIdentity ( );
gluLookAt ( 10, 10, 10, 0, 0, 0, 0, 1, 0 );
// Affichage du repère
glBegin ( GL_LINES );
glColor3d ( 1, 0, 0 ); glVertex3d ( 0, 0, 0 ); glVertex3d ( 0, 0, 10 );
glColor3d ( 0, 1, 0 ); glVertex3d ( 0, 0, 0 ); glVertex3d ( 0, 10, 0 );
glColor3d ( 0, 0, 1 ); glVertex3d ( 0, 0, 0 ); glVertex3d ( 10, 0, 0 );
glEnd ( );
// On rotate la scène
glRotated ( a+=temps.ecoule*50, 0, 1, 0 );
// On affiche 1 ou plusieurs Cube (emplacement randomizé)
if ( ramage )
{
int i;
for ( i = 0 ; i < NB_CUBES ; i++ )
{
glTranslated ( rand ( ) % 1000 / 25.0 - 20, rand ( ) % 1000 / 25.0 - 20, rand ( ) % 1000 / 25.0 - 20 );
Cube ( );
}
}
else Cube ( );
glutSwapBuffers ( );
}
glutPostRedisplay ( );
}
//------------------------------------------------------------------------------
// Fonction Principale
//------------------------------------------------------------------------------
int main ( int argc, char *argv[ ], char *envp[ ] )
{
// Création de la fenêtre OpenGL
glutInit ( &argc, argv );
glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
glutInitWindowSize ( 512, 512 );
glutInitWindowPosition ( 50, 50 );
glutCreateWindow ( "" );
// Lancement de l'application OpenGL
InitGL ( );
glutDisplayFunc ( Draw );
glutIdleFunc ( Draw ); //<- le programme continue de fonctionner si vous réduisez la fenêtre
glutReshapeFunc ( Reshape );
glutKeyboardFunc ( GestionClavier );
glutMainLoop ( );
return 0;
}
//------------------------------------------------------------------------------
// End Of File
//------------------------------------------------------------------------------