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[] )
{
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return 1;
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",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height,
SDL_WINDOW_OPENGL);
if(window == nullptr)
return 1;
...
}
é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"
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_Window *window= SDL_CreateWindow("gKit", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height, SDL_WINDOW_OPENGL);
if(context == nullptr)
return 1;
SDL_GLContext context= SDL_GL_CreateContext(window);
if(context == nullptr)
return 1;
SDL_GL_SetSwapInterval(1);
#ifndef NO_GLAD
gladLoadGL();
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;
while(SDL_PollEvent(&event))
{
if(event.type == SDL_QUIT)
done= true;
else if(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
done= true;
}
{ ... }
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.
int quit( ) { ... }
int render( ) { ... }
int main( int argc, char *argv[] )
{
{
printf(
"[error] init failed.\n");
return 1;
}
{
if(render() == 0)
break;
SDL_GL_SwapWindow(window);
}
quit();
return 0;
}
Context create_context(Window window)
cree et configure un contexte opengl
int events(Window window)
fonction interne de gestion d'evenements.
void release_window(Window window)
destruction de la fenetre.
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().
Window create_window(const int w, const int h, const int major, const int minor)
creation d'une fenetre pour l'application.
void release_context(Context context)
detruit le contexte openGL.
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 :
{
public:
TP( ) :
App(1024, 640) {}
};
int main( int argc, char *argv[] )
{
return 0;
}
int run()
execution de l'application.
int render()
a deriver pour afficher les objets. renvoie 1 pour continuer, 0 pour fermer l'application.
int quit()
a deriver pour detruire les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
int init()
a deriver pour creer les objets openGL. renvoie -1 pour indiquer une erreur, 0 sinon.
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
int render( )
{
...
{
}
}
void clear_key_state(const SDL_Keycode key)
desactive une touche du clavier.
int key_state(const SDL_Keycode key)
renvoie l'etat d'une touche du clavier. cf la doc SDL2 pour les codes.
int screenshot(const char *filename)
enregistre le contenu de la fenetre dans un fichier. doit etre de type .png / .bmp
à 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...
options :
window.h définit aussi quelques options utiles pour configurer la fenêtre de l'application :
- on peut interdire à la fenêtre à changer de taille avec window_resize( false ). par défaut, les fenêtres peuvent changer de taille, ce qui est équivalent à utiliser utiliser window_resize( true );
- on peut aussi demander la conversion automatique des couleurs RGB linéaires calculées par les fragment shaders vers le standard sRGB avec window_srgb( true ). par défaut, il n'y a pas de conversion. cf premières lumières, éclairer un objet, sRGB et gamma pour les explications.
- et on peut activer le MSAA pour lisser les arêtes géométriques en utilisant plusieurs points par pixel. il suffit d'utiliser window_msaa( 4 ) ou window_msaa( 8 ) pour utiliser 4 ou 8 points de filtrage. attention, plus il y a de points de filtrage MSAA, plus dessiner des objets ralenti...
voila les options par défaut :
bool window_srgb()
renvoie le mode srgb de la fenetre de l'application.
bool window_resize()
renvoie vrai si la fenetre peut etre redimensionnee.
int window_msaa()
renvoie le nombre de samples MSAA.
ou