# Programmation avec la SDL/Le renderer

Certes, il est utile de savoir manipuler des fenêtres, mais lorsqu'il n'y a rien dedans, on est bien loin de la création d'un jeu en 2D ou d'un logiciel quelconque... C'est pourquoi dans ce chapitre nous allons enfin commencer à remplir notre fenêtre. Ceci, nous allons le faire grâce à un renderer, c'est-à-dire un moteur de rendu pour notre fenêtre. Pour parler plus clairement, le renderer est en fait l'espace où nous allons pouvoir dessiner, écrire... Or, pour qu'un dessin ou un mot s'affiche à l'écran, il faut faire un rendu, c'est-à-dire créer l'image finale que l'on veut afficher, puis l'afficher.

## Création du renderer

On ne peut associer qu'un seul renderer à chaque fenêtre (celle-ci étant créée avant le renderer). En effet, la fenêtre ne nécessite qu'un seul contexte d'affichage... S'il y en avait trop, ils se marcheraient dessus.
Avec la SDL, le renderer est contenu dans une structure appelée SDL_Renderer. Elle contient les informations du contexte d'affichage de la fenêtre. Ainsi, pour créer un renderer, nous devons en tout premier lieu déclarer un pointeur de SDL_Renderer. La raison de la déclaration d'un pointeur plutôt que la structure directement est purement pratique : toutes les fonctions de la SDL utilisent des pointeurs de renderer et non la structure en elle-même pour des raisons d'optimisation. En effet, il est moins long de passer en argument une adresse plutôt qu'une structure SDL_Renderer assez lourde. Il est donc plus simple d'utiliser un pointeur. La structure pointée contiendra ensuite les informations renvoyées par la fonction SDL_CreateRenderer que voici :
Tout d'abord, vous remarquerez que cette fonction renvoie un pointeur de SDL_Renderer, ou NULL en cas d'erreur. Observons maintenant les arguments de cette fonction :
* window : c'est la fenêtre à laquelle associer votre renderer.
* index : Ne vous en occupez pas et donnez -1 comme argument.
* flags : À force de les voir, vous devriez commencer à y être habitué. Voici les valeurs qu'ils peuvent prendre :
SDL_RendererFlags :
| | Description |
| SDL_RENDERER_ACCELERATED | Utilise l'accélération matérielle, c'est à dire la carte graphique. Il est fortement recommandé de le mettre puisque c'est un des atouts majeurs de la SDL 2.0 par rapport à la SDL 1.2 et surtout parce que c'est beaucoup plus rapide. |
| SDL_RENDERER_PRESENTVSYNC | Synchronise le rendu avec la fréquence de rafraîchissement. La fréquence de rafraîchissement correspond au nombre de fois où l'écran est mis à jour par seconde. Ainsi, le rendu sera synchronisé avec celle-ci. |
| SDL_RENDERER_TARGETTEXTURE | Utilisez ce flag lorsque vous souhaitez associer un renderer à une texture. |
| SDL_RENDERER_SOFTWARE | Utilisez ce flag lorsque vous souhaitez associer un renderer à une surface. |
Vous vous doutez bien que lorsqu'on crée un renderer, il faut aussi le détruire... Et pour cela, nous allons utiliser SDL_DestroyRenderer.
Maintenant, vous devriez savoir sans l'aide de personne déclarer un renderer. Mais au cas où vous n'y arriveriez pas par vous-même, voici le code complet de la création d'un renderer avec tout ce qui va avec :

### Créer un renderer et une fenêtre

Pour finir ce paragraphe sur la création d'un renderer, sachez que l'on peut créer un renderer et une fenêtre à partir d'une même fonction, SDL_CreateWindowAndRenderer :
Les arguments sont les mêmes que pour la création d'une fenêtre à ceci près que l'on vous demande un pointeur de pointeur de fenêtre et un pointeur de pointeur de renderer. Si vous souhaitez savoir les flags qu'utilise le renderer créé avec cette fonction, utilisez SDL_RendererGetInfo qui est présentée dans le paragraphe suivant. Avec les mêmes variables que nous avons utilisées dans le code ci-dessus, la création du renderer devient donc :

## Le rendu

Nous savons maintenant comment créer un moteur de rendu. Seulement, il nous faudrait savoir faire un rendu pour qu'il soit utile. Il faut bien comprendre que lorsque vous modifierez le renderer, bien que vous ne sachiez pas encore le faire, cette modification n'apparaîtra pas à l'écran tant que vous n'aurez pas fait de rendu. Nous allons employer pour cela la fonction SDL_RenderPresent dont voici le prototype :
Vous emploierez donc cette fonction ainsi :
Étant donné que tout ce que vous avez à savoir sur le rendu consiste en la fonction SDL_RenderPresent, nous allons nous occuper de deux fonctions en relation directe avec celui-ci : SDL_GetRenderer et SDL_GetRendererInfo.
La première fonction sert simplement à récupérer le renderer associée à la fenêtre que l'on passe en argument.
Cette fonction peut être utile lorsque vous gérez plusieurs fenêtre en même temps. En effet, vous pourrez alors n'utiliser qu'une seule variable de type SDL_Renderer de telle sorte que celle-ci pointe vers le renderer de la fenêtre active. À chaque fois que le focus passe d'une fenêtre à une autre, il vous suffit donc de récupérer le renderer de la fenêtre active.
SDL_GetRendererInfo va nous permettre d'obtenir des informations sur le renderer (le nom de la fonction est très explicite, mais on ne sait jamais).
La fonction renvoie 0 en cas de succès et une valeur négative en cas d'échec. Ensuite, la fonction demande l'adresse du renderer dont vous voulez obtenir des informations puis un pointer de SDL_RenderInfo. Comme vous vous en doutez, c'est une structure adaptée pour contenir des informations sur un renderer. Voici les champs qu'elle contient :
* char* name : le nom du renderer
* Uint32 flags : les flags associés au renderer. Attention, ceux-ci fonctionnent d'une manière un peu particulière (voir exemple).
Comme vous l'avez vu, il n'y a pas grand chose à dire sur le rendu. En revanche, vous allez vous rendre compte que le renderer offre de relativement nombreuses possibilités que nous allons maintenant explorer.

## Dessiner sur le renderer

Le renderer, en plus d'être un simple moteur de rendu, permet de créer des formes basiques de manière plus optimisée que si l'on collait une texture dessus (vous saurez bientôt ce que cela veut dire).

### Les couleurs

Pour commencer, voyons comment changer la couleur de fond du renderer, qui est noire par défaut. Nous devons tout d'abord définir la couleur avec laquelle nous allons travailler grâce à SDL_SetRenderDrawColor. La couleur que nous allons déterminer ici est en fait une couleur qui servira pour tous les dessins que vous ferez avec le renderer jusqu'à ce que nous la changions.
Cette fonction renvoie 0 en cas de succès et une valeur négative en cas d'échec. Vous devez être en train de vous dire qu'une fois de plus, il va falloir faire une gestion des erreurs. Mais puisque vous commencez à présent à faire partie des initiés de la SDL, un grand secret va vous être révélé : il n'y a pas grand monde qui vérifie les erreurs à la sortie de chaque fonction. Par conséquent, dorénavant, vous serez épargné de la vérification d'erreurs. Par contre, si un jour votre programme plante, n'oubliez pas ce long début de cours à devoir écrire une vérification des erreurs pour chaque fonction, car à ce moment, celle-ci vous sera probablement d'une grande utilité.
Revenons à notre fonction. Elle nous demande en argument le renderer pour lequel nous voulons déterminer la couleur de travail, mais aussi quatre Uint8 répondant aux noms de r, g, b et a. Ceux-ci vont nous permettre de donner à la fonction la couleur avec laquelle nous allons remplir le renderer. Arrêtons nous sur le fonctionnement des couleurs.
R, g, b et a correspondent en anglais à red, green, blue et alpha, ce qui en français correspond à rouge, vert, bleu et alpha (la transparence). Un Uint8 faisant un octet, r,v,b et a prennent des valeurs situées entre 0 et 255. Afin de créer la quasi totalité des couleurs différentiables par l’œil, nous allons combiner le rouge, le vert et le bleu comme lorsqu'en peinture vous obtenez du orange en mélangeant le rouge et le jaune. Le problème ici, c'est que ce ne sont pas les trois couleurs primaires habituelles. En fait, tout cela n'est qu'une histoire de physique. Lorsque vous faites de la peinture, vous faites ce que l'on appelle une synthèse soustractive des couleurs si bien que lorsque vous mélangez du rouge, du jaune et du bleu, vous obtenez du noir. Au contraire, ici, vous allez utiliser une synthèse additive : rouge + vert + bleu = blanc. Ceci est très résumé, mais étant donné que ce livre traite de la SDL et non de la restitution des couleurs, nous vous recommandons pour approfondir de lire la restitution des couleurs : cela ne vous prendra pas longtemps et vous comprendrez mieux comment fonctionne ce rgba.
Le a, quant à lui correspond à la transparence de la couleur du pixel. Plus il est près de 0, plus la couleur est transparente, plus il est près de 255, plus la couleur est opaque. Pour en savoir plus sur la transparence, reportez-vous au chapitre sur la transparence.
Voici un tableau qui vous donnera une idée de la manière de combiner les couleurs pour obtenir la teinte que vous désirez.
Les couleurs :
| Rouge | Vert | Bleu | Code hexadécimal |
| 0 | 0 | 0 | 0x000000 |
| 80 | 80 | 80 | 0x505050 |
| 160 | 160 | 160 | 0xA0A0A0 |
| 255 | 255 | 255 | 0xFFFFFF |
| 160 | 0 | 160 | 0xA000A0 |
| 0 | 0 | 255 | 0x0000FF |
| 0 | 255 | 255 | 0x00FFFF |
| 0 | 255 | 0 | 0x00FF00 |
| 255 | 255 | 0 | 0xFFFF00 |
| 255 | 140 | 0 | 0xFF8C00 |
| 255 | 0 | 0 | 0xFF0000 |
| 255 | 0 | 255 | 0xFF00FF |
| 255 | 144 | 208 | 0xFFA0A0 |
Certes, vous pouvez créer beaucoup plus de couleur (il y en a 16 777 216). Pour bien comprendre comment fonctionne ce codage des couleurs rgb, vous pouvez aller vous amuser à en créer sur le Gimp ou sur Paint...
Si vous souhaitez savoir quelle couleur vous utilisez,vous pouvez employer la fonction SDL_GetRenderDrawColor.
Cette fonction renvoie les valeurs habituelles pour la gestion des erreurs (0 en cas de succès, valeur négative en cas d'erreur). Elle s'utilise ainsi :
Revenons à notre objectif de changer la couleur de fond du renderer. Pour remplir celui-ci avec la couleur déterminée par SDL_SetRenderDrawColor, utilisez la fonction SDL_RenderClear
Cette fonction renvoie 0 en cas de succès et une valeur négative en cas d'erreur. Vous pourrez trouver un exemple d'utilisation de celle-ci à la partie "Points, lignes et rectangles".

### Points et lignes

Vous avez peut-être été un petit peu abusé lorsqu'on vous a annoncé que l'on pouvait dessiner sur le renderer. En fait, tout ce qu'on peut faire, c'est remplir des points (en l’occurrence des pixels), des lignes et des rectangles avec la couleur que vous avez choisie avec SDL_SetRenderDrawColor.
Avant de voir comment "dessiner", rappelons comment fonctionne les positions sur la SDL, et en informatique en général. L'origine du repère se trouve en haut à gauche et l'axe des abscisses est dirigé vers la droite et l'axe des ordonnés est dirigé vers le bas. Forts de ces données, la fonction de coloriage d'un point va nous paraître extrêmement simple.
Rappelons, si c'est encore nécessaire, que le int renvoyé prend la valeur 0 en cas de succès et une valeur négative en cas d'erreur. Ensuite, on donne à la fonction le renderer sur lequel on veut dessiner et bien sûr la position en x et en y du point.
Jusqu'ici, tout est très simple. Mais lorsqu'on veut remplir beaucoup de points, cette fonction n'est pas très adaptée. Or, cela tombe bien, il existe une fonction faite pour remplir plusieurs points à la fois, SDL_RenderDrawPoints.
Pour le retour et le premier argument, cette fonction est identique à SDL_RenderDrawPoint. En revanche, elle nécessite un tableau de structure SDL_Point et la taille de ce tableau. La structure SDL_Point ne comporte que deux champs : x et y.
Par exemple, on peut coder une ligne commençant à la position (4;100) et se terminant à la position (30;100) ainsi (vous pouvez essayer de le faire vous-même avant de regarder ce code) :
Même s'il n'est pas d'un grand effort de dessiner une ligne, la SDL nous facilite le travail en nous offrant une fonction toute prête :
Vous savez à quoi sert l'entier renvoyé, ne nous y attardons plus. Pour les arguments, vous savez déjà presque tout. La fonction vous demande le renderer sur lequel dessiner et les positions des deux extrémité de la ligne (les pixels aux extrémités sont inclus dans la ligne). Le code suivant est donc l'équivalent du code utilisant les SDL_Point ci-dessus :
C'est tout de même plus simple qu'avec SDL_RenderDrawPoints...
La SDL propose une fonction pour dessiner un chemin. Elle permet en fait de relier plusieurs points.
Comme vous avez pu le remarquer, cette fonction appelle les mêmes arguments que la fonction SDL_RenderDrawPoints. En fait, pour tracer un chemin ou un polygone, il suffit de créer un tableau de SDL_Point de telle sorte que le premier point soit le départ de la forme que vous voulez créer et que le point suivant soit celui que vous voulez relier au précédent.
Par exemple, vous pouvez, pour vous entraîner, essayer de tracer un triangle quelconque.
Voici la solution :

### Rectangles

Ce paragraphe est très important pour la suite de ce livre et, lorsque vous aurez fini de le lire, il faut que vous sachiez bien vous servir des rectangles avec la SDL. En effet, la SDL ne permet globalement que de gérer des rectangles et il faut que vous sachiez comment ceux-ci sont stockés avant de lire le chapitre suivant. Cependant, n'ayez pas peur, vous allez voir qu'il n'y a rien de difficile.
Dans la SDL, il existe une structure spécialement faite pour stocker un rectangle qui s'appelle SDL_Rect. Celle-ci comporte quatre champs, tous des int, que vous connaissez déjà :
* x et y : la position du rectangle par rapport au repère de votre fenêtre
* w : la largeur du rectangle
* h : la hauteur du rectangle
Voici comment on le déclare :
Maintenant que vous savez comment fonctionne la structure SDL_Rect, retournons à notre renderer. Sur celui-ci, vous allez pouvoir soit dessiner un rectangle, soit remplir un rectangle. La différence réside dans le fait que si vous dessinez le rectangle, celui-ci n'est pas plein (cela paraît évident mais ceux qui utilise depuis longtemps la SDL n'avait jusqu'à présent à leur disposition que la fonction de remplissage).
Voici donc la fonction qui permet de dessiner un seul rectangle et, au passage, celle qui permet d'en dessiner plusieurs dans la même foulée.
Attention, n'oubliez pas que pour dessiner un seul rectangle, il vous faut envoyer un pointeur de celui-ci. La fonction permettant de dessiner plusieurs rectangles fonctionne de manière identique à celle permettant de dessiner plusieurs points.
Enfin, voici les fonctions permettant de remplir un ou plusieurs rectangles. Celles-ci sont presque identique aux fonctions de dessin d'un rectangle, c'est pourquoi vous avez seulement besoin du prototype. Après avoir étudié celles-ci, nous en aurons fini avec les fonctions de dessin.
Il y a encore quelque chose que vous devez savoir sur les rectangles avec le renderer. Vous pouvez créer une zone de travail sur votre renderer, semblable aux sélections pour ceux qui ont déjà travaillé sur un logiciel de graphisme. Lorsque vous allez associer une zone de travail à votre renderer, vous allez interdire l'accès à tous les pixels à l'extérieur du rectangle et définir un nouveau repère : désormais, la position (0;0) correspond au coin en haut à gauche du rectangle délimitant votre zone de travail. Cette fonctionnalité est très utile par exemple lorsque vous avez créé un menu placé en haut de votre fenêtre et que vous ne voulez plus vous en occuper, ou bien si vous développez une interface graphique avec des boîtes d'outil et que vous ne voulez vous occuper que de ces boîtes à un moment donné.
Étant donné que cette fonction ressemble à celles que nous avons vu, nous n'allons nous attarder que sur le second argument. En effet, il y a un piège : la fonction demande seulement un pointeur du rectangle délimitant la zone sur laquelle vous désirez travailler mais ce rectangle est un peu spécial. En effet, il semble y avoir une contradiction dans la SDL puisque le repère permettant de déterminer la position du rectangle prend son origine dans le coin en bas à gauche du renderer et que par conséquent, l'axe des abscisses est dirigé vers la droite et l'axe des ordonnées vers le haut. Ce défaut sera peut-être corrigé d'ici peu, le seul moyen de le savoir est de tester la fonction pour se rendre compte.
Il manque encore quelque chose : lorsqu'on a une fonction "set", on a souvent "get" qui l'accompagne. Vous pouvez donc récupérer la zone sur laquelle vous travaillez grâce à la fonction SDL_RenderGetViewport :

## Deux petits TPs

Le chapitre que nous venons de voir était dense et représente une partie importante dans l'affichage avec la SDL. Or, comme vous le savez, en programmation, la théorie seule ne suffit pas. Voici donc deux travaux pratiques que vous pouvez réaliser afin de vous entraîner. Le second est un petit peu plus délicat et nécessite quelques connaissances mathématiques bien que celles-ci soient rudimentaires : un niveau de seconde générale devrait suffire. Sachez cependant que réaliser le second TP est plus formateur que de réaliser le premier.
Bien évidemment, il y a une infinité de manière de coder. Une solution est proposée, mais votre code même s'il n'est pas identique peut être tout à fait correct.

### Un échiquier

Dans ce TP, nous allons simplement afficher un échiquier dans une fenêtre non redimensionable de dimensions 800x800 pixels. Pour mieux délimiter les cases, des bordures rouges d'épaisseur un pixel séparent celles-ci. À vous de jouer !

### Un peu de géométrie

Le but de ce TP est de dessiner un octogone régulier inscrit dans un cercle lui-même inscrit dans le carré que constituera la fenêtre. Tout d'abord, vous devez définir une fenêtre carrée, non redimensionnable (on ne va pas compliquer les choses). Une définition de 800x800 conviendra sans doute.
Étant donné que ce n'est peut-être pas très claire pour certains, nous allons ici donner quelques astuces pour y réussir:
Pour le cercle, n'oubliez pas que, si l'on prend comme repère orthonormé le centre de notre fenêtre, la position des points d'un cercle est $x=cos(\theta )$ et $y=sin(\theta )$. Pour calculer le sinus et le cosinus, utilisez les fonction cos et sin de la bibliothèque math.h. Attention, les angles des fonctions cos et sin sont en radian.
L'angle formé par deux points consécutifs de l'octogone et le centre du cercle est de 45 degrés (${\frac {\pi }{4}}$ radians). Vous pouvez en déduire à l'aide de cosinus et de sinus la position de tous les points de l'octogone puisqu'ils se trouvent sur le cercle.
Pour ceux qui voudraient en faire encore un petit peu plus, pourquoi ne pas essayer de tracer les diagonales de l'octogone?
Pour simplifier le code, tracez l'octogone en même temps que vous tracez les diagonales, ne réutilisez pas la fonction SDL_RenderDrawLines... Contentez vous de SDL_RenderDrawLine