gKit2 light
Loading...
Searching...
No Matches
écrire une application openGL

cf tuto1.cpp

une application openGL est composée de plusieurs éléments :

  • une fenêtre pour voir ce que l'on dessine,
  • un contexte openGL pour dessiner,
  • 3 fonctions :
    • init( ) pour créer les objets que l'on veut dessiner,
    • quit( ) pour détruire les objets openGL crées dans init( ), à la fermeture de l'application,
    • render( ) pour afficher / dessiner quelque chose.

ces 3 fonctions sont appelées dans le main.

La classe App permet également d'écrire une application avec une conception légèrement différente mais avec les mêmes fonctionnalités.

création de la fenêtre et du contexte openGL

La création d'une fenêtre et la gestion des évènements clavier / souris dépend du système : windows, mac os, linux, etc. la librairie SDL2 permet d'écrire un seul code portable qui fonctionnera sur tous ces systèmes.

La démarche est plutot directe, il faut initialiser SDL2, créer la fenêtre, créer un contexte openGL et traiter les évènements claviers / souris.

étape 1 : intialiser SDL2

#include "SDL2/SDL.h"
int main( int argc, char *argv[] )
{
// init SDL
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return 1; // erreur lors de l'init de sdl
// enregistre le destructeur de sdl
atexit(SDL_Quit);
...
}
Note
il est nécessaire d'utiliser cette signature de main pour raison de portabilité avec SDL2. certains systèmes utilisent une autre fonction comme point d'entrée dans le programme, comme windows, par exemple, qui utilise WinMain pour les applications qui ouvrent une fenêtre. cette signature permet à SDL de créer le bon point d'entrée de manière portable avec la librairie SDLmain.

étape 2 : créer une fenêtre

C'est la fonction SDL_CreateWindow( ) qui s'en charge, il faut lui fournir plusieurs paramètres : un titre, la position et la taille de la fenêtre et une option indiquant qu'openGL doit pouvoir dessiner dedans.

int main( int argc, char *argv[] )
{
...
int width= 1024;
int height= 640;
...
SDL_Window *window= SDL_CreateWindow("gKit", // titre
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, // position choisie par le système
width, height, // dimensions
SDL_WINDOW_OPENGL); // option pour openGL
if(window == nullptr)
return 1; // erreur lors de la creation de la fenetre ou de l'init de sdl2
...
}

étape 3 : créer et configurer le contexte openGL

Il existe plusieurs versions d'openGL, il faut indiquer laquelle choisir, ainsi qu'activer ou pas, le mode debug pour obtenir des messages d'erreurs. c'est la fonction SDL_GL_SetAttribute( ) qui permet de configurer tous ces paramètres avant de créer la fenêtre.

Pour obtenir un affichage "propre", il faut pouvoir dessiner dans une image qui n'est pas affichée dans la fenêtre, sinon on peut observer un effet de cisaillement (cf screen tearing wikipedia) assez désagréable, c'est encore SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, ...) qui permet d'activer cette option. Il faut également indiquer que le changement entre les 2 images, celle qui est affichée et celle dans laquelle openGL dessine, se fait en fonction de la vitesse d'affichage de l'écran, cf SDL_GL_SetSwapInterval(1); et utiliser SDL_GL_SwapWindow(); pour provoquer l'échange, lorsque l'application a fini de dessiner.

dernière étape, avant de pouvoir utiliser les fonctions d'openGL, il faut finir d'importer les symboles définis par la librairie dynamique openGL... selon les systèmes c'est inutile (mac os) ou obligatoire (windows). c'est la librairie GLAD qui s'en charge.

voila à quoi ressemble ce code d'initialisation :

#include "SDL2/SDL.h"
#include "glad.h"
// selectionner les options...
// version 3.3
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
// activer le mode debug
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
// choisir une version moderne d'openGL, ie version core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// dessiner et afficher des images differentes...
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// creer la fenetre avec les options openGL
SDL_Window *window= SDL_CreateWindow("gKit", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height, SDL_WINDOW_OPENGL);
if(context == nullptr)
return 1; // oops, ça ne fonctionne pas...
// creer le contexte
SDL_GLContext context= SDL_GL_CreateContext(window);
if(context == nullptr)
return 1; // ca ne marchera jamais... jamais...
// attendre l'ecran pour dessiner
SDL_GL_SetSwapInterval(1);
#ifndef NO_GLAD
// importer les fonctions d'openGL sur les systemes ou c'est necessaire.
gladLoadGL();
// purge les erreurs eventuellement generees par l'init...
while(glGetError() != GL_NO_ERROR) {;}
#endif

étape 4 : traiter les évènements

La fenêtre est ouverte, le contexte openGL est crée, il ne reste plus qu'à dessiner quelque chose, mais il faut aussi savoir quand s'arrêter ! et savoir que l'utilisateur a bougé la souris, cliqué, ou enfoncé une touche sur le clavier, ce qui permettra de déplacer la caméra, des objets, etc.

C'est SDL_PollEvents() qui récupère ces informations. il ne reste plus qu'à réagir en fonction. Au minimum, il faut fermer la fenêtre :

int main( int argc, char *argv[] )
{
...
bool done= false;
while(!done)
{
SDL_Event event;
// recuperer un evenement a la fois, poll event renvoie faux lorsqu'ils ont tous ete traite
while(SDL_PollEvent(&event))
{
if(event.type == SDL_QUIT)
done= true; // sortir si click sur le bouton 'fermer' de la fenetre
else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
done= true; // sortir si la touche esc / echap est enfoncee
}
// dessiner quelque chose avec openGL
{ ... }
// presenter / montrer le resultat, ie echanger les images associees a la fenetre.
SDL_GL_SwapWindow(window);
}
...
}

attention : ne pas oublier le SDL_GL_SwapWindow(), qui permet d'afficher ce que l'on vient de dessiner.

code complet dans tuto1.cpp et tuto1GL.cpp.

résumé :

voila, c'est un peu long, il y a pas mal de doc à lire sur le fonctionnement de SDL2, mais ce n'est pas bien compliqué. et comme c'est toujours la même chose, les fonctions create_window( ), create_context(), events() documentées dans window.h font exactement ca. ce qui permet de simplifier pas mal le code et de se concentrer sur openGL.

#include "window.h"
// creation des objets opengl
int init( ) { ... }
// destruction des objets opengl
int quit( ) { ... }
// dessiner une nouvelle image
int render( ) { ... }
int main( int argc, char *argv[] )
{
Window window= create_window(1024, 640); // etape 1 : creer la fenetre (+ les dimensions de la fenêtre)
Context context= create_context(window); // etape 2 : creer un contexte opengl pour pouvoir dessiner
// etape 3 : creation des objets openGL de l'application
if(init() < 0)
{
printf("[error] init failed.\n");
return 1;
}
// etape 4 : affichage de l'application, tant que la fenetre n'est pas fermee. ou que render() ne renvoie pas 0
// appelle render( ) pour dessiner une nouvelle image, 60 fois par seconde.
while( events() )
{
if(render() == 0)
break;
// presenter / montrer le résultat
SDL_GL_SwapWindow(window);
}
// etape 5 : nettoyage
quit(); // detruire les objets openGL crees par l'application
release_context(context);
release_window(window);
return 0;
}
Context create_context(Window window)
cree et configure un contexte opengl
Definition window.cpp:344
int events(Window window)
fonction interne de gestion d'evenements.
Definition window.cpp:168
void release_window(Window window)
destruction de la fenetre.
Definition window.cpp:314
void printf(Text &text, const int px, const int py, const char *format,...)
affiche un texte a la position x, y. meme utilisation que printf().
Definition text.cpp:140
Window create_window(const int w, const int h, const int major, const int minor, const int samples)
creation d'une fenetre pour l'application.
Definition window.cpp:249
void release_context(Context context)
detruit le contexte openGL.
Definition window.cpp:404
int init(std::vector< const char * > &options)

Si cette version typée C ne vous convient pas, il y a également une classe de base à dériver, App. voici un exemple équivalent :

#include "app.h"
class TP : public App
{
public:
// constructeur : donner les dimensions de la fenetre, et eventuellement la version d'openGL.
TP( ) : App(1024, 640) {}
// creation des objets openGL de l'application
int init( ) { ... }
// destruction des objets openGL, à la fin de l'application
int quit( ) { ... }
// dessiner une nouvelle image.
int render( ) { ... }
};
int main( int argc, char *argv[] )
{
TP tp;
tp.run();
return 0;
}
classe application.
Definition app.h:20
int run()
execution de l'application.
Definition app.cpp:36
Definition alpha.cpp:58
int render()
a deriver pour afficher les objets. renvoie 1 pour continuer, 0 pour fermer l'application.
Definition alpha.cpp:132
int quit()
a deriver pour detruire les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
Definition alpha.cpp:124
int init()
a deriver pour creer les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
Definition alpha.cpp:64

dans la mesure du possible, les fonctions de window.h ne dupliquent pas / n'abstraient pas les fonctionnalités de sdl2, vous pouvez les utiliser directement. par exemple :

  • SDL_GetTicks() pour connaitre le temps écoulé depuis le lancement de l'application,
  • SDL_GetMouseState() pour récupérer la position de la souris et l'état de ses boutons.

window.h définit par contre des utilitaires pour simplifier l'écriture d'applications interactives. par exemple, si vous voulez modifier la position d'un objet en appuyant sur une touche, les fonctions key_state( ) et clear_key_state( ) permettent de le faire très simplement, sans avoir besoin de modifier la gestion des évènements (cf run(), App::run() et events()).

autre exemple d'utilisation : faire une capture d'écran de l'application

#include "window.h"
#include "texture.h"
int render( )
{
...
if(key_state('s'))
{
screenshot("screenshot.png");
}
}
void clear_key_state(const SDL_Keycode key)
desactive une touche du clavier.
Definition window.cpp:46
int key_state(const SDL_Keycode key)
renvoie l'etat d'une touche du clavier. cf la doc SDL2 pour les codes.
Definition window.cpp:40
int screenshot(const char *filename)
enregistre le contenu de la fenetre dans un fichier. doit etre de type .png / .bmp
Definition texture.cpp:178

à quoi sert le clear_key_state( ) ? vu que draw( ) est appelée 60 fois par seconde, il est probable que la touche reste enfoncée pendant une bonne dixaine d'appels, reinitialiser l'état de la touche permet d'éviter d'enregistrer 10 fois l'image...