L'un des avantages de la SFML est d'offrir un contexte de rendu OpenGL grâce à la classe sf::Window. Cela permet de réaliser des applications 3D portables sans avoir à se préoccuper de la création du dit contexte. Dans ce cadre, il peut-être intéressant de connaître le nombre de FPS pour se faire une idée de la vitesse d'exécution de notre programme. Cela peut-être fait relativement simplement en utilisant quelques fonctions OpenGL et c'est ce dont traite ce tutoriel.
Le tutoriel Afficher les FPS sans charger de font de Mindiell traite du même sujet en utilisant les fonctions de la SFML. En particulier, cela implique d'utiliser la classe sf::RenderWindow pour pouvoir dessiner les FPS dans la fenêtre. La méthode qui est décrite ici présente l'intérêt d'être utilisable directement avec une sf::Window (et, plus généralement, dans n'importe quelle application basée sur OpenGL) et toujours sans charger de sf::Font.
Dans la suite, nous créons une classe CFps qui permet d'afficher les FPS dans une sf::Window. Nous ajoutons quelques fonctions afin de changer la couleur et la position de l'affichage.
Note: il s'agit d'une capture d'écran relative au code donnée au bas de ce tutoriel. En dehors des FPS, rien n'est affiché d'où le nombre important.
Rien de très original ici, il s'agit de la définition de la classe sfe::CFps et le nom de classe alternatif sfe::Fps. Cette classe dispose de trois fonctions membres en plus du constructeur:
#ifndef H_SFE_FPS_MESEIRA_16042010 #define H_SFE_FPS_MESEIRA_16042010 #include <SFML/Window.hpp> namespace sfe { class CFps { public: // Constructeur CFps(); // Fonctions membres void Draw(sf::Window & win); void SetColor(float red, float green, float blue); void SetOffset(size_t hoff, size_t voff); private: // Couleur float m_red, m_green, m_blue; // Offset de la position size_t m_hoff, m_voff; }; typedef CFps Fps; } // namespace sfe #endif /* H_SFE_FPS_MESEIRA_16042010 */
OpenGL permet de dessiner directement sur la fenêtre à partir d'un masque. Ce masque est un tableau de bits, chacun d'entre eux correspondant à un pixel. Si le bit vaut 0, le pixel n'est pas affecté, si le bit vaut 1, le pixel est “allumé”. Un dessin valant mieux qu'un long discours, voici le masque pour le chiffre 4:
Les lignes du masque se lisent de gauche à droite et de bas en haut. La première est donc celle du bas et contient les deux octets 0×00 (00000000 en binaire) et 0x0F (00001111). La deuxième est identique et nous continuons ainsi jusqu'à la dernière (celle du haut) qui correspond aux octets 0xF0 et 0x0F. Ansi, nous obtenons pour le chiffre 4 le masque suivant:
{0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F}
Note: il faut faire de même pour tous les chiffres de 0 à 9. Les masques correspondants sont donnés dans le code complet plus bas sous la forme d'un tableau raw_number de 10 masques.
Nous avons ici choisi d'afficher les chiffres sur une zone de 16 pixels de large et 24 pixels de haut. Les masques sont donc constitués de 16×24 bits, ce qui équivaut à 48 octets. Ces octets sont alignés dans la mémoire et pour indiquer cette organisation à l'ordinateur, il faut faire appel à une fonction de la famille glPixelStore* .
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Pour définir la couleur d'affichage des FPS, c'est du classique.
glColor3f(m_red, m_green, m_blue);
Cependant, il faut faire attention à déclarer cette couleur avant de positionner les FPS. En effet, les appels à glColor* entre le positionnement et l'affichage des données brutes seront ignorés. Pour placer les FPS sur la fenêtre, OpenGL propose plusieurs solutions. Nous utiliserons ici la fonction glWindowPos2i. Elle prend deux paramètres entiers qui correspondent aux coordonnées du premier pixel du masque (donc celui en bas à gauche) dans le repère de la fenêtre (l'origine étant le coin en bas à gauche). Cette fonction est apparue à partir de OpenGL 1.4, nous définissons donc GL_GLEXT_PROTOTYPES à 1 au cas où.
glWindowPos2i(m_hoff, m_voff);
Note: par défaut, la couleur d'affichage est le blanc et les FPS sont placés en bas à gauche de la fenêtre avec une marge de 20 pixels.
CFps::CFps() : m_red(1.0), m_green(1.0), m_blue(1.0), m_hoff(20), m_voff(20) { }
Nous arrivons enfin à l'affichage proprement dit des FPS. Tout d'abord, nous récupérons la valeur courante pour la sf::Window win et nous la convertissons en chaîne de caractères:
size_t fps = static_cast<size_t>(1.0f/win.GetFrameTime()); char buffer[16]; sprintf(buffer, "%u", fps);
Il ne nous reste alors plus qu'à afficher chiffre après chiffre. Pour cela, nous prenons un à un les caractères de buffer et nous leur retranchons la valeur du caractère '0' de façon à faire coïncider le chiffre à afficher avec le masque correspondant. Dans le cas (critique) où le nombre de FPS serait inférieur à 1.0, nous affichons simplement zéro.
if (fps) { size_t i = 0; while (buffer[i] != '\0') glBitmap(16, 24, 0.0, 0.0, 20.0, 0.0, raw_number[buffer[i++] - '0']); } else glBitmap(16, 24, 0.0, 0.0, 0.0, 0.0, raw_number[0]);
Pour l'affichage, nous avons utilisé la fonction glBitmap avec les paramètres suivants:
Dernière chose, nous avons modifié certains paramètres pour procéder à l'affichage: la façon dont sont stockées les données en mémoire (glPixelStorei), la position courante (glWindowPos2i et glBitmap) et la couleur (glColor3f). Afin de faciliter l'intégration de cette classe au sein d'un code quelconque sans se soucier de son impact, il est important de restaurer les états fixés par l'utilisateur avant l'affichage des FPS.
GLint pixel_store;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &pixel_store);
GLfloat raster[4];
glGetFloatv(GL_CURRENT_RASTER_POSITION, raster);
[...]
glRasterPos4fv(raster);
glPixelStorei(GL_UNPACK_ALIGNMENT, pixel_store);
Note: la couleur étant un paramètre moins critique, nous ne nous en occupons pas mais la démarche serait la même.
Mettons ensemble tout ce qui vient d'être raconté et complétons-le avec les fonctions SetColor et SetOffset. Pour ces dernières, nous nous contentons d'affecter les valeurs passées aux paramètres correspondants.
#define GL_GLEXT_PROTOTYPES 1 #include "sfe_fps.hpp" #include <cstdio> // sprintf #include <cstring> // size_t static const GLubyte raw_number[10][48] = { // 0 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 1 {0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F}, // 2 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 3 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 4 {0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F}, // 5 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 6 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 7 {0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 8 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // 9 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} }; namespace sfe { CFps::CFps() : m_red(1.0), m_green(1.0), m_blue(1.0), m_hoff(20), m_voff(20) { } // Affiche les FPS de la fenêtre win dans celle-ci // Etat modifié: Color void CFps::Draw(sf::Window & win) { // Obtenir le nombre de FPS size_t fps = static_cast<size_t>(1.0f/win.GetFrameTime()); char buffer[16]; sprintf(buffer, "%u", fps); // Indiquer la façon dont nos données sont organisées (et enregistrer le // réglage précédent) GLint pixel_store; glGetIntegerv(GL_UNPACK_ALIGNMENT, &pixel_store); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Enregistrer la position d'affichage courante GLfloat raster[4]; glGetFloatv(GL_CURRENT_RASTER_POSITION, raster); // Fixer la couleur et la position d'affichage des FPS glColor3f(m_red, m_green, m_blue); glWindowPos2i(m_hoff, m_voff); // Afficher les FPS if (fps) { size_t i = 0; while (buffer[i] != '\0') glBitmap(16, 24, 0.0, 0.0, 20.0, 0.0, raw_number[buffer[i++] - '0']); } else glBitmap(16, 24, 0.0, 0.0, 0.0, 0.0, raw_number[0]); // Restoration des paramètres glRasterPos4fv(raster); glPixelStorei(GL_UNPACK_ALIGNMENT, pixel_store); } void CFps::SetColor(float red, float green, float blue) { m_red = red; m_green = green; m_blue = blue; } void CFps::SetOffset(size_t hoff, size_t voff) { m_hoff = hoff; m_voff = voff; } } // namespace sfe
Voici un exemple minimal d'utilisation de la classe sfe::Fps. A chaque tour de la boucle principale, nous effaçons le contenu de la fenêtre en noir et nous affichons les FPS en rouge et en haut à gauche.
#include <SFML/Window.hpp> #include "sfe_fps.hpp" int main() { sf::Window app(sf::VideoMode(800, 600, 32), "Openg GL", sf::Style::Close); sfe::Fps fps; fps.SetColor(1.0, 0.0, 0.0); // en rouge fps.SetOffset(20, app.GetHeight() - 44); // en haut à gauche glClearColor(0.0, 0.0, 0.0, 0.0); while (app.IsOpened()) { sf::Event ev; while (app.GetEvent(ev)) { if (ev.Type == sf::Event::Closed) app.Close(); } glClear(GL_COLOR_BUFFER_BIT); fps.Draw(app); app.Display(); } return 0; }
Comme d'habitude sauf qu'il n'y a pas besoin ici de la partie Graphics de la SFML.
$ g++ -O2 -lsfml-window -lsfml-system -o fps sfe_fps.cpp main.cpp $ ./fps
N'hésitez pas à posez vos questions ou à faire vos commentaires sur le forum.