GERBELOTBARILLON.COM

Parce qu'il faut toujours un commencement...

La bibliothèque SDL v3.0


Qu'est-ce donc ?

Si vous avez toujours voulu programmer un jeu ou une application multimédia, et surtout, si vous désirez que votre application soit multi plateformes, la solution probablement idéale se nomme SDL (Simple Directmedia Library). Avec cette bibliothèque de programmation vous serez à même de gérer les divers aspects relatifs aux jeux vidéo que sont les sprites, la musique, la gestion des entrées/sorties...et même une éventuelle intégration avec OpenGL pour faire de jolis dessins en 3D...

Sommaire

Installation sous Linux Debian et dérivées

Rendez-vous sur le github du projet https://github.com/libsdl-org/SDL/releases/latest. Là soit vous téléchargez la version qui correspond à votre environnement (s'il existe), soit vous cliquez sur le lien SDL en haut à gauche ou vous allez ici : Page principale du projet et vous téléchargez le lien Git. C'est cette option que nous choisissons pour nous permettre de compiler la bibliothèque SDL3. Avec ce lien et votre terminal, faites

git clone https://github.com/libsdl-org/SDL.git
pour récupérer les sources dans un dossier créé automatiquement et qui s'appellera SDL (original non ?).

Avant de lancer la compilation, assurez-vous de disposer des outils requis que sont cmake, g++ et gcc. Testez simplement en faisant

$ cmake --version
	  g++ --version
	  gcc --version
Si l'un des trois venait à manquer, utilisez le gestionnaire de paquets de votre distribution linux pour les installer avec $ sudo apt install build-essential.

Cette fois c'est parti... Commencez par créer un dossier build dans le dossier de SDL puis allez dans ce dossier. Exécutez la commande cmake ... Après quelques secondes de tests, cmake devrait afficher que tout s'est bien passé et que le buildfile a été généré dans votre dossier. Maintenant faites simplement make et attendez la fin de la compilation. Si tout s'est bien passé, vous devriez obtenir les informations signalant que la bibliothèque SDL3 a été créée.

[100%] Linking C static library libSDL3_test.a
	  [100%] Built target SDL3_test
      
Enfin, faites $ sudo make install pour installer les bibliothèques et les fichiers d'inclusion dans le système.

Dans le dossier build vous trouvez le fichier sdl3.pc qui contient les éléments d'informations pour le programme pkg-config. C'est un outil pratique dans la compilation afin de définir automatiquement les répertoires dans lesquels le compilateur doit aller chercher ses bibliothèques de compilation. Pour vérifier ce qu'il contient utilisez les options --libs pour les bibliothèques et --cflags pour les chemins des fichiers include :

$ pkg-config --libs sdl3
	  -L/usr/local/lib -Wl,-rpath,/usr/local/lib -Wl,--enable-new-dtags -lSDL3

	  $ pkg-config --cflags sdl3
	  -I/usr/local/include
      
Et donc pour compiler un programme on pourrait faire (si votre programme s'appelle test.cpp) :
g++ test.cpp -o test `pkg-config --cflags --libs sdl3`

Je vous laisse faire le test avec le fichier d'exemple disponible dans le wiki dans l'exemple CreateWindow. Pour les fainéants, voici le fichier en question :


// Example program:
// Using SDL3 to create an application window

#include 
#include 

int main(int argc, char* argv[]) {

    SDL_Window *window;                    // Declare a pointer
    bool done = false;

    SDL_Init(SDL_INIT_VIDEO);              // Initialize SDL3

    // Create an application window with the following settings:
    window = SDL_CreateWindow(
        "An SDL3 window",                  // window title
        640,                               // width, in pixels
        480,                               // height, in pixels
        SDL_WINDOW_OPENGL                  // flags - see below
    );

    // Check that the window was successfully created
    if (window == NULL) {
        // In the case that the window could not be made...
        SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Could not create window: %s\n", SDL_GetError());
        return 1;
    }

    while (!done) {
        SDL_Event event;

        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_EVENT_QUIT) {
                done = true;
            }
        }

        // Do game logic, present a frame, etc.
    }

    // Close and destroy the window
    SDL_DestroyWindow(window);

    // Clean up
    SDL_Quit();
    return 0;
}
      

Composants additionnels pour SDL2

Telle que, la SDL2 est capable de manipuler des images, de jouer des sons ou de la musique, de manipuler des joysticks ou de faire du multi thread... Cependant, pour aller plus loin, de nombreux add-ons ont été développés afin d'étendre ses capacités. Nous pourrons alors

Pour notre exemple, nous allons télécharger la bibliothèque SDL_Image qui va nous permettre d'ouvrir et de manipuler des images dans de multiples formats. Cela sera la même chose pour toutes les extensions dont SDL dispose. Toutes les commandes vont être similaires à celles utilisées lors de la compilation de la bibliothèque principale SDL3.

Commençons par nous rendre sur le site du projet SDL_Image. Copiez le lien git : https://github.com/libsdl-org/SDL_image.git. Rendez-vous dans un dossier de votre disque et faites $ git clone https://github.com/libsdl-org/SDL_image.git.

Maintenant faites

$ mkdir build
	  $ cd build
	  $ cmake ..
	  $ sudo make install
	  $ pkg-config sdl3-image
	  $ pkg-config --cflags --libs sdl3-image
      

Répétez les opérations pour les fichiers de bibliothèques annexes suivants (si vous en avez besoin) :

Initialisation de la SDL 3.0

Initialisation de la bibliothèque

Le code de base de la SDL est le suivant :

#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
	if (SDL_Init(SDL_INIT_VIDEO) != 0) {
		fprintf(stderr, "Erreur d'initialisation de SDL : %s\n", SDL_GetError());
		return EXIT_FAILURE;
	}

	SDL_Quit();

	return EXIT_SUCCESS;;
}
La fonction principale est celle d'initialisation de la biblothèque : SDL_Init(Uint32 flags). Elle prend en paramètre les flags qui permettront de mettre en place les différentes briques de la SDL.
Flags Description
SDL_INIT_TIMERSystème de gestion des temps
SDL_INIT_AUDIOSystème de gestion de l'audio
SDL_INIT_VIDEOSystème de gestion de l'affichage. Initialise également automatiquement le gestionnaire d'événements.
SDL_INIT_JOYSTICKSystème de gestion des joysticks. Initialise également automatiquement le gestionnaire d'événements.
SDL_INIT_GAMECONTROLLERSystème de gestion des contrôleurs de jeux en général. Initialise également le système de gestion des joysticks.
SDL_INIT_EVENTSSystème de gestion des événements
SDL_INIT_EVERYTHINGInitialise tous les sous-systèmes en une seule fois.

Il est possible d'utiliser plusieurs flags en même temps en les associant avec l'opérateur '|'.

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)
La fonction retourne une valeur 0 si tout s'est bien passé ou une autre valeur si une erreur survient. Dans ce dernier cas, vous pouvez obtenir le détail de l'erreur en utilisant la fonction SDL_GetError() qui renvoie l'information sous la forme d'une constante chaîne.

En fin de programme, n'oubliez pas de désallouer les ressources utilisées par SDL en utilisant la fonction SDL_Quit(). Vous pouvez mettre en place la fonction atexit(SDL_Quit); juste après l'ouverture de la SDL afin de ne pas oublier ces désallocations...

Création d'affichages

Une fois la SDL initialisée, pour afficher des éléments sur l'écran il faut mettre en place des fenêtres d'affichage. Cela s'effectue assez simplement en utilisant SDL_Window * SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags). Le prototype de la fonction reprend les éléments d'identification de fenêtre avec un titre, une position (x, y), une dimension (w, h) et des éléments de comportement de la fenêtre (les flags). Comme pour SDL_Init(), ceux-ci peuvent être combinés par un '|' (OR'ed). Les plus utilisés sont les suivants :

Flags Description
SDL_WINDOW_FULLSCREENFenêtre en mode plein écran
SDL_WINDOW_FULLSCREEN_DESKTOPFenêtre en plein écran avec la résolution du bureau
SDL_WINDOW_OPENGLFenêtre utilisable avec un environnement OpenGL
SDL_WINDOW_VULKANFenêtre utilisant Vulkan pour le rendu à la place d'OpenGL (Vulkan est une évolution de ce dernier pour tenter d'unifier OpenGL et OpenGL ES sous Windows, Linux, MacOS).
SDL_WINDOW_METALMode spécifique à Apple...
SDL_WINDOW_HIDDENFenêtre non visible
SDL_WINDOW_BORDERLESSPas de décoration sur la fenêtre. Pour fermer l'affichage le code devra le prendre en charge.
SDL_WINDOW_RESIZABLEFenêtre redimensionnable (avec la décoration qui le permet)
SDL_WINDOW_MINIMIZEDFenêtre créée visible mais réduite en barre des tâches
SDL_WINDOW_MAXIMIZEDFenêtre créée avec une taille maximale sur l'écran (mais pas plein écran)
SDL_WINDOW_INPUT_GRABBEDA la création de la fenêtre, celle-ci capture la saisie utilisateur par défaut.
SDL_WINDOW_ALLOW_HIGHDPILa fenêtre créée doit l'être en mode High-DPI (si supporté et SDL > 2.0.1)

La fonction SDL_CreateWindow() renvoie l'identification de la nouvelle fenêtre ou bien NULL si une erreur est survenue. Dans ce cas, utilisez SDL_GetError() pour en savoir plus sur ce qui s'est mal passé. Les erreurs généralement rencontrées sont :

En fin de programme, n'oubliez pas de libérer les ressources utilisées par la fenêtre en utilisant la fonction SDL_DestroyWindow().

Le bout de code suivant, qui se situe après l'initialisation réussie de la bibliothèque SDL2, permet d'ouvrir une fenêtre d'une dimension 800x600 et qui s'ouvrira à une position déterminée par le système d'exploitation et avec une prise en charge de OpenGL.


SDL_Window *window = NULL;

window = SDL_CreateWindow("Fenêtre OpenGL en SDL",
				SDL_WINDOWPOS_UNDEFINED,
				SDL_WINDOWPOS_UNDEFINED,
				800, 600, SDL_WINDOW_OPENGL);
if (window == NULL) {
	fprintf(stderr, "La fenêtre ne peut pas être créée : %s\n", SDL_GetError());
	return EXIT_FAILURE;
}

// A partir d'ici, la fenêtre est ouverte. Il va falloir traiter les événements arrivant sur cette fenêtre.
...

SDL_DestroyWindow(window);

// Fin du programme
...

Gestion des paramètres de la fenêtre

Une fois la fenêtre créée, il est tout à fait possible de modifier ses paramètres à savoir, son titre, sa taille, sa position et les différents flags. La bibliothèque SDL met à notre disposition une série de fonctions dont la syntaxe est la suivante :

Titre de la fenêtre

const char *SDL_GetWindowTitle(SDL_Window *window);
void SDL_SetWindowTitle(SDL_Window *window, const char *title);

Position de la fenêtre

void SDL_GetWindowPosition(SDL_Window *window, int *x, int *y);
void SDL_SetWindowTitle(SDL_Window *window, int x, int y);
Les paramètres x et y peuvent prendre les valeurs SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED en plus des valeurs numériques.

Dimensions de la fenêtre

void SDL_GetWindowSize(SDL_Window *window, int *w, int *h);
void SDL_SetWindowSize(SDL_Window *window, int w, int h);
Les paramètres x et y peuvent prendre les valeurs SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED en plus des valeurs numériques.

Les flags de la fenêtre

Uint32 SDL_GetWindowFlags(SDL_Window *window);
La gestion des flags est une méthode générale dans les environnements de programmation en C. Pour gérer ces éléments il faut les combiner avec '|' lors de l'affectation des valeurs, et les associer avec '&' pour déterminer s'ils sont définis ou non dans la valeur renvoyée.
// Affectation des valeurs avec '|' (OR)
Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS;
SDL_Window *window = SDL_CreateWindow("SDL", 0, 0, 800, 600, flags);

// Lecture des variables
flags = SDL_GetWindowFlags(window);
if (flags & SDL_WINDOW_RESIZABLE) printf("La fenêtre est redimensionnable.");
Contrairement à ce que nous avons dit au début du paragraphe, il n'existe pas de fonction pour passer des flags à la fenêtre. Par contre SDL met à notre disposition une série de fonctions permettant d'agir sur les paramètres de la fenêtre : Les paramètres x et y peuvent prendre les valeurs SDL_WINDOWPOS_UNDEFINED ou SDL_WINDOWPOS_CENTERED en plus des valeurs numériques.

Gestion du rendu graphique

L'affichage dans une fenêtre passe par un composant intermédiaire nommé le renderer. Son rôle est d'être aujourd'hui placé au plus proche des éléments de calcul graphique tels les GPU, pour les meilleures performances possibles. Pour créer un Renderer, nous faisons appel à la fonction SDL_Renderer *SDL_CreateRenderer(SDL_Window *window, int index, Uint32 flags);. Selon le prototype, la fonction, en plus de la fenêtre, prend un index en paramètre (-1 est généralement le meilleur choix car automatiquement déterminé par SDL) ainsi qu'un ensemble de flags. Le renderer renvoyé peut être NULL si une erreur s'est produite. En fin d'utilisation, comme tout autre objet SDL, le renderer devra librérer ses ressources par void SDL_DestroyRenderer(SDL_Renderer *renderer);.

Flags Description
SDL_RENDERER_SOFTWARELe rendu est par défaut généré par logiciel. Solution la plus basique car géré par le CPU et les données sont stockées dans la mémoire vive.
SDL_RENDERER_ACCELERATEDLe rendu utilise l'accélération matérielle. Choix par défaut si la valeur de flags n'est pas définie. Les données sont stockées en mémoire vidéo, plus rapide que la mémoire vive.
SDL_RENDERER_PRESENTVSYNCSynchronisation avec le taux de rafraîchissement de l'écran.
SDL_RENDERER_TARGETTEXTURELe renderer supporte le rendu sur texture.

Puisque nous avons besoin de créer une fenêtre et un renderer, autant le faire en une seule fois. SDL a prévu cela en mettant à disposition la fonction int SDL_CreateWindowAndRenderer(int width, int height, Uint32 window_flags, SDL_Window **window, SDL_Renderer **renderer);. La fonction renvoie 0 si réussite, ou -1 sur erreur. ATTENTION, il faut bien passer un pointeur sur un pointeur pour que la fonction puisse modifier le pointeur de fenêtre que l'on passe à cette fonction.

SDL_Window *window;
SDL_Renderer *renderer;

if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer)) {
	fprintf(stderr, "Erreur de création de la fenêtre et du renderer : %s\n", SDL_GetError());
	return EXIT_FAILURE;
}

// Reste du programme
...
///

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

La couleur dans SDL

SDL utilise une simple structure SDL_Color qui est définie par :

Dessiner avec SDL

Les éléments utilisables avec SDL sont :

Les rectangles sont la base des gestions de rendu avec SDL. Ils permettent de gérer les collisions entre objets graphiques et disposent de fonctions spécifiques pour cela :

SDL_Bool SDL_HasIntersection(SDL_Rect *A, SDL_Rect *B);
La fonction renvoie une valeur booléenne si les deux rectangles se touchent.

SDL_Bool SDL_IntersectRect(SDL_Rect *A, SDL_Rect *B, SDL_Rect *result);
Tout comme SDL_HasIntersection(), la fonction SDL_IntersectRect() prend deux rectangles en paramètres et renvoie un pointeur sur le rectangle qui correspond à l'intersection des rectangles.

SDL_Bool SDL_PointInRect(SDL_Point *p, SDL_Rect *r);
La fonction renvoie une valeur booléenne vraie si le point est dans le rectangle.

Etapes dans la gestion du dessin avec SDL2

Maintenant nous savons : Allons faire un tour sur la façon dont le renderer fonctionne plus précisément. Il faut considérer ce dernier comme un support sur lequel nous allons :

Dessiner des points et des lignes

Afficher une image

Pour afficher une image, il faut tout d'abord la charger en mémoire et ensuite la placer sur la plateforme de rendu graphique (le GPU de la carte graphique dans le meilleur des cas). Par défaut, la fonction SDL_Surface *SDL_LoadBMP(const char *file) prend le nom d'un fichier de format BMP en entrée et fourni un pointeur sur une Surface, ou NULL si erreur.

Une fois la surface disponible, il est recommandé de la transférer au GPU sous la forme d'une texture par SDL_Texture *SDL_CreateTextureFromSurface(SDL_Renderer *renderer, SDL_Surface *surface). Une fois la Surface transférée sur le système de texture, il faut penser à libérer la mémoire occupée en utilisant la fonction void SDL_FreeSurface(SDL_Surface *surface).

Afficher la texture revient ensuite à :

En fin de traitement, il ne faut pas oublier de libérer les ressources dans l'ordre inverse de leur allocation :

SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

Autres formats d'images

SDL n'est pas seulement capable de traiter des images au format BMP, mais peut tout à fait afficher des images au format PNG, JPG, GIF, PCX, SVG, TGA, TIFF, ... La liste complète est disponible ici SDL_Image. Charger une image en mémoire peut aboutir à une Surface (dans la mémoire vive) ou à une Texture (dans la mémoire graphique). Cette dernière est l'option à privilégier par défaut.

Références