Afficher les FPS dans une sf::Window avec OpenGL

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.

Aperçu

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.

Le fichier d'en-tête

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:

  • void Draw(sf::Window & win) affiche le nombre de FPS relatif à la fenêtre win dans celle-ci,
  • void SetColor(float red, float green, float blue) définit les composantes rouge, verte et bleue de la couleur d'affichage des FPS,
  • void SetOffset(size_t hoff, size_t voff) décale l'affichage des FPS de hoff pixels horizontalement et voff verticalement (l'origine étant le coin inférieur gauche de la fenêtre).

sfe_fps.hpp

#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 */

L'affichage des FPS

Format des données

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);

Couleur et position

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)
  {
  }

Affichage des FPS

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:

  • 16, 24 taille en pixels du masque,
  • 0.0, 0.0 décalage par rapport à la position courante (celle indiquée par glWindowPos2i),
  • 20.0, 0.0 décalage de la position courante d'affichage après avoir dessiné le masque (l'espace entre deux chiffres est donc de 20-16=4 pixels).

Sauvegarde des paramètres

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.

Code complet

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.

sfe_fps.cpp

#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

Exemple d'utilisation

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.

main.cpp

#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;
}

Compilation et lancement

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

Commentaires

N'hésitez pas à posez vos questions ou à faire vos commentaires sur le forum.

 
fr/tutoriels/openglfps.txt · Last modified: 2010/04/17 18:28 by meseira
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki