GERBELOTBARILLON.COM

Parce qu'il faut toujours un commencement...

La bibliothèque SDL v2.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

Le moyen le plus simple est d'utiliser les paquets de la distribution en faisant sudo apt-get install libsdl2-dev
Cela va installer le nécessaire et suffisant pour pouvoir ensuite compiler les programmes avec la SDL2, ainsi que les fichiers d'inclusion d'entête depuis le dossier /usr/include/SDL2.

La bibliothèque seule offre déjà beaucoup de facilités dans la gestion des images et des sons mais elle peut être complétée par des bibliothèques plus spécialisées :

Toutes ces bibliothèques s'installent simplement par sudo apt-get install libsdl2-<nom de la lib = gfx|image|mixer|net|ttf>-dev

Test de l'installation

Pour vérifier que votre installation est opérationnelle, il suffit de compiler le programme suivant (à éditer avec votre éditeur favori tel que Emacs,nano, vim ou autre):

#include <SDL2/SDL.h>

int main(int argc, char **argv)
{
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_Quit();
	return(0);
}
Si vous avez appelé le programme précédent sdl2test.c, il suffit d'utiliser la ligne de compilation suivante :
gcc sdl2test.c -o sdl2test -lSDL2 -lSDL2main
Si vous exécutez ce programme, il ne devrait rien se passer de précis mais la compilation ne devrait pas afficher de message d'erreur. Si c'était le cas, essayez d'utiliser la compilation par fichier de configuration avec gcc sdl2text.c $(sdl2-config --cflags --libs)

Installation de SDL 2.0 sous Windows

Commencez par vous rendre sur le site www.libsdl.org et sélectionnez le téléchargement de la version 2.0. Celle-ci est suffisamment robuste pour que l'on ne s'intéresse plus aujourd'hui à l'ancestrale version 1.2 (qui fonctionnait très bien cependant). Sur la page de téléchargement, choisissez la version qui correspond à votre système d'exploitation et au compilateur que vous utilisez. Pour l'exemple, nous considérons que vous utilisez l'environnement de développement Visual Studio Express 2013 version Desktop (qui est gratuit et disponible ici : http://www.microsoft.com/france/visual-studio/essayez/download.aspx en vous déplaçant vers le bas de la page pour trouver le lien de téléchargement).

Par défaut sont disponibles les versions pour Visual studio (32 ou 64 bits), pour MinGW (en versions 32 et 64 bits également), pour MacOS X. Pour Linux, il y a tellement de cas possibles qu'il est recommandé de se tourner vers les packages installables directement depuis le système de mise à jour de la distribution utilisée.

Si l'on prend l'exemple de Visual Studio, cliquez sur le lien SDL2-devel-2.x.x-VC.zip afin de récupérer le kit de développement SDL. Pour faire simple, décompressez-le directement dans un dossier de base de votre ordinateur (comme C:\SDL2 par exemple). Vous obtiendrez alors deux dossiers : include et lib dans lesquels se trouvent les bibliothèques et fichiers d'inclusion requis pour compiler vos programmes.

Utiliser Visual Studio avec SDL

Maintenant que nous avons installé les bibliothèques de la SDL dans le système, pour compiler avec Visual Studio nous allons devoir procéder à quelques configurations. Il ne sera pas nécessaire de refaire cette configuration à chaque fois car VS nous permet de sauvegarder nos réglages sous la forme d'un template. Il suffira donc de le charger pour avoir un environnement prêt pour SDL. Pour commencer nous avons besoin d'un projet C++ vide.

Chemin pour les fichiers Include

Pour que le compilateur puisse trouver les fichiers d'entête, ouvrez la fenêtre des propriétés et déroulez la section C/C++. Dans la rubrique Général, renseignez le champ Autres répertoires Include avec le chemin correspondant à l'emplacement où la bibliothèque SDL a été décompressée.

Configurer le chemin de la bibliothèque de compilation

Après le paramétrage du compilateur, passons au linker, qui va avoir besoin des biblithèque d'éditions de liens propres à SDL2. Pour cela, dans la section Editeur de liens, à la rubrique Général, précisez l'emplacement du chemin de la bibliothèque SDL2 dans le champ Répertoires de bibliothèques supplémentaires. Vous pouvez spécifier la version 32 ou 64 bits de SDL2. Dans l'exemple nous allons rester sur la version 32 bits.

Si vous souhaitez tout de même utiliser la version 64 bits de la SDL, pensez à paramétrer votre éditeur de liens afin qu'il produise du code 64 bits. Cela s'effectue dans la section Editeur de liens, à la rubrique Avancé. Choisissez MachineX64(/MACHINE:X64) au lieu de la valeur par défaut MachineX86(/MACHINE:X86) dans le champ Ordinateur cible.

Configurer les dépendances de bibliothèques

Non seulement l'éditeur de liens doit savoir où chercher les bibliothèques, mais il doit en plus être capable de les utiliser en tant que dépendances de linkage afin de récupérer les fonctions exportées de ces bibliothèques et permettre au programme final de se générer correctement. Ces dépendances sont à définir dans la section Editeur de liens, rubrique entrée. Vous pouvez supprimer les élements déjà présents dans le champ Dépendances supplémentaires pour les remplacer par les deux bibliothèques indispensables à SDL2 : SDL2.lib et SDL2main.lib.

Sélection du sous-système cible

Visual Studio est capable de produire un exécutable ciblé sur un emplacement précis : une ROM pour un BIOS EFI, un programme en mode natif, un programme qui respecte tous les standards POSIX... Ceux qui nous intéressent sont les options Console et Windows. Avec un mode Console, vous aurez une fenêtre DOS qui va s'afficher en plus de votre fenêtre de programme SDL. C'est pratique en mode debug pour tracer les éléments directement avec des sorties du genre printf(). En mode Windows, vous n'aurez pas cette fenêtre. Le choix est simple : tant que le programme est en construction il faut utiliser le sous-système Console. Après, une fois l'application plus aboutie, vous pourrez générer le programme final avec un sous-système Windows.

Création d'un template

Pour ne pas avoir à refaire cette configuration à chaque nouveau projet, il suffit de faire Menu Fichiers->Exporter le modèle afin d'arriver sur un assistant qui va vous permettre de créer un modèle réutilisable. Il sera ensuite disponible dans la rubrique C/C++ du gestionnaire de projets de Visual Studio.


Premier programme SDL2

Pour savoir si toutes les configurations sont opérationnelles, il suffit de tester avec un petit programme SDL dont le rôle sera simplement d'initialiser la SDL2, d'attendre une seconde, puis de fermer correctement l'ensemble.

#include <SDL.h>

int main(int argc, char **argv)
{
	if (SDL_Init(SDL_INIT_VIDEO) !=0)
	{
		fprintf(stdout, "Erreur avec SDL_Init : %s\n", SDL_GetError());
		return(-1);
	}
	
	SDL_Delay(1000);

	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.

Commençons par nous rendre sur le site du projet SDL_Image : https://www.libsdl.org/projects/SDL_image/. Ici vous trouverez les runtimes Windows en version x86 et x64, ainsi que les bibliothèques de développement pour Visual Studio. Il vous suffit de télécharger la version de développement (car elle inclut également les DLL pour Windows), de la décompresser et de placer les différents éléments dans les dossiers de la SDL2, à l'endroit où vous l'avez placée en suivant les indications du présent document. Ainsi les éléments du dossier include vont se mettre dans le dossier include du répertoire de la SDL2, de même pour les éléments du dossier lib qui vont aller dans le dossier lib de la SDL2.

Avant de pouvoir générer un programme fonctionnel, il ne faut pas oublier de spécifier à l'éditeur de liens la bibliothèque à utiliser pour récupérer les symboles utilisés dans votre programme. Pour cela, comme précédemment, ouvrez le panneau des propriétés de la solution en cours d'édition, puis dans la section Editeur de liens, rubrique entrée, rajouter le nom du fichier se terminant par .lib et correspondant à votre nouvelle bibliothèque. Dans notre exemple, il faut rajouter SDL2_image.lib.

Installation de SDL 2.0 avec MinGW

Pour ceux qui souhaitent utiliser le compilateur GCC du bundle MinGW sous Windows, il est bien évidemment possible d'utiliser la SDL2 avec ce compilateur. Pour cela, commencez par télécharger la bibliothèque SDL2 sur cette page : Page de téléchargement de SDL2 en choisissant bien la version de développement pour MinGW au format tar.gz.

Désarchivez le fichier .tar.gz pour obtenir un fichier .tar. Désarchivez également de fichier .tar pour obtenir un dossier qui contient essentiellement deux éléments intéressants :

Chacun de ces dossiers contient les sous-dossiers bin, include et lib qu'il va falloir copier dans l'arborescence de votre installation MinGW qui contient les mêmes dossiers bin, include et lib. Choisissez le dossier de destination en fonction de la version de votre compilateur et de votre OS. Pour ma part tout est copié dans le dossier C:\MinGW\x86_64-w64-mingw32.

Pour celles et ceux qui disposent de l'environnement MSys64, avec MinGW intégré, il faudra procéder aux mêmes opérations de copie des dossiers, par exemple dans C:\msys64\mingw64\x86_64-w64-mingw32.

Pensez bien à paramétrer le PATH de votre environnement pour inclure le dossier bin du compilateur pour que celui-ci puisse trouver les DLL et exécutables correctement.

Compilation d'un programme avec SDL2

Pour compiler un programme incluant des appels fonctionnels à la SDL2, il suffit de faire : gcc prg.c -o prg.exe -lmingw32 -lSDL2main -lSDL2
Si vous n'avez pas paramétré vos chemins d'accès, il est possible de préciser au compilateur l'emplacement des dossiers contenant les éléments requis par gcc prg.c -o prg.exe -Ic:\mingw\include\sdl2 -Lc:\mingw\lib -lmingw32 -lSDL2main -lSDL2

Bibliothèques complémentaires

La bibliothèque SDL2 dispose de différentes bibliothèques annexes permettant d'enrichir l'aspect fonctionnel avec :

L'installation de ces bibliothèques suit exactement le même principe que pour la version principale de la SDL2 (copie des fichiers bin, include et lib). Pour la compilation d'un programme exploitant ces bibliothèques supplémentaires, il suffit d'ajouter une référence à la compilation par -lSDL2_net pour une compilation avec le gestion du réseau par SDL2 par exemple. Ou bien -lSDL2_image si c'est la gestion des images par SDL2 qui est utilisée.

Initialisation de la SDL 2

La bibliothèque SDL permet l'usage des GPU pour la mise en place de la gestion des affichages graphiques. Tout est basé sur un mode fenêtré, qui remplace l'ancien mode plein écran utilisé par défaut par la SDL. Le principe est relativement simple et suit le schéma suivant :

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