SDL Gestion Video

Un article de Mangue.org, l'encyclopéde libre.


La gestion vidéo vue par SDL

SDL travaille sur le concept très répandu des surfaces. C'est assez simple en fait, tel DirectX, lorsque vous initialisez un mode vidéo (nous verrons ceci juste après), vous créez une surface qui correspond à un pointeur vers une structure SDL_Surface. Cette dernière contient les diverses caractéristiques que vous avez demandé pour cette surface. Par conséquent une surface est une zone mémoire système ou vidéo à laquelle vous pouvez accéder respectivement directement ou indirectement. La structure SDL_Surface est définie comme suit :

 
typedef struct SDL_Surface {
	Uint32 flags;				/* Read-only */
	SDL_PixelFormat *format;		/* Read-only */
	int w, h;				/* Read-only */
	Uint16 pitch;				/* Read-only */
	void *pixels;				/* Read-write */
	int offset;				/* Private */
 
	/* Hardware-specific surface info */
	struct private_hwdata *hwdata;
 
	/* clipping information */
	SDL_Rect clip_rect;			/* Read-only */
	Uint32 unused1;				/* for binary compatibility */
 
	/* Allow recursive locks */
	Uint32 locked;				/* Private */
 
	/* info for fast blit mapping to other surfaces */
	struct SDL_BlitMap *map;		/* Private */
 
	/* format version, bumped at every change to invalidate blit maps */
	unsigned int format_version;		/* Private */
 
	/* Reference count -- used when freeing surface */
	int refcount;				/* Read-mostly */
} SDL_Surface;

L'un des champs les plus utiles est format, qui est un pointeur vers les caractéristiques des pixels de la surface :

 
 
/* Everything in the pixel format structure is read-only */
typedef struct SDL_PixelFormat {
	SDL_Palette *palette; /* NULL si BitsPerPixel>8 */
	Uint8  BitsPerPixel; /* 8,16,24 ou 32 */
	Uint8  BytesPerPixel; /* 1,2,3,4 */
	Uint8  Rloss; /* Precision loss of each color component (2[RGBA]loss) */
	Uint8  Gloss;
	Uint8  Bloss;
	Uint8  Aloss;
	Uint8  Rshift; /* Binary left shift of each color component in the pixel value */
	Uint8  Gshift;
	Uint8  Bshift;
	Uint8  Ashift;
	Uint32 Rmask; /* Binary mask used to retrieve individual color values */
	Uint32 Gmask;
	Uint32 Bmask;
	Uint32 Amask;
 
	/* RGB color key information */
	Uint32 colorkey;
	/* Alpha value information (per-surface alpha) */
	Uint8  alpha;
} SDL_PixelFormat;

Il serait intéressant que vous consultiez la doc officielle afin de voir l'intérêt de tels champs et comment les exploiter. Mon but n'étant pas de traduire cette doc (quoique un jour...), je ne m'attarderai pas là-dessus. Je vais vous apprendre à créer la surface principale qui correspond finalement à la surface d'affichage sur votre écran.

 
SDL_Surface *screen;
 
screen = SDL_SetVideoMode(640,480,16,SDL_FULLSCREEN | SDL_HWSURFACE);   
 
if(screen == NULL){   
      fprintf(stderr,"Impossible d'allouer une surface SDL %s\n", SDL_GetError()); 
      exit(1); 
}

Alors qu'est-ce que je viens de faire ici ? J'appelle la fonction SDL_SetVideoMode() qui me permet de créer la surface vidéo principale. Comment marche cette fonction ? Elle attend quatre arguments : la largeur de la surface, sa hauteur, la profondeur (bpp) demandée et le type de surface. SDL respectera toujours les deux premiers arguments, mais les deux derniers seront liés à ce que votre système supporte. Si jamais ce dernier ne peut offrir les caractéristiques demandées, SDL tentera d'émuler celles-ci mais au détriment des performances, si SDL ne peut émuler de telles caractéristiques, la fonction échoue et retourne NULL. La valeur du quatrième argument correspond à une combinaison binaire de drapeaux :

 
SDL_ANYFORMAT	0x10000000 /* Autorise n'importe quel mode vidéo */
SDL_SWSURFACE	0x00000000 /* Place la surface en mémoire système */
SDL_HWSURFACE	0x00000001 /* Place la surface en mémoire vidéo */
SDL_ASYNCBLIT	0x00000004 /* Autorise les transferts vidéos (blits) asynchrone */
SDL_HWPALETTE	0x20000000 /* La surface possède un accès exclusif à la palette vidéo */
SDL_DOUBLEBUF	0x40000000 /* Active le doublebuffering */
SDL_FULLSCREEN	0x80000000 /* Mode plein écran */
SDL_OPENGL      0x00000002 /* Crée un contexte de rendu pour OpenGL */
SDL_OPENGLBLIT	0x0000000A /* Crée un contexte de rendu pour OpenGL et l'utiliser pour des transferts vidéos */
SDL_RESIZABLE	0x00000010 /* La fenêtre est redimensionnable */
SDL_NOFRAME	0x00000020 /* Crée un contexte vidéo sans les décorations du WM */

En général dans vos petites applications vous spécifierez vous-même le mode vidéo que vous désirez. Mais à posteriori, vous ne devez pas présumer des caractéristiques des systèmes qui lancerons vos programmes. Aussi SDL fournit un jeu de fonctions permettant de poser d'abord des questions au système puis de chercher le meilleur mode par rapport à vos demandes et ce que le système supporte.

 
SDL_VideoInfo *SDL_GetVideoInfo(void);
SDL_Rect **SDL_ListModes(SDL_PixelFormat *format, Uint32 flags);
int SDL_VideoModeOK(int width, int height, int bpp, Uint32 flags);
char *SDL_VideoDriverName(char *namebuf, int maxlen);

La première fonction vous donne un pointeur vers les informations concernant les caractéristiques hardware (vidéo) de votre système. Si cette fonction est appelée avant SDL_SetVideoMode(), le champ vfmt de la structure de retour contiendra la valeur du meilleur mode vidéo de votre système. La seconde fonction vous retourne un pointeur vers la liste complète des modes vidéo supportés par votre système. La troisième fonction vous permet de tester la validité d'un mode avant de l'activer. La dernière fonction vous donne sous forme textuelle le nom du driver vidéo sur lequel vous travaillez.

Une explication s'impose ici en ce qui concerne les différences entre le mode software et le mode hardware vidéo. Le mode software n'est plus vraiment souvent d'actualité car les performances des cartes vidéo actuelles permettent largement d'activer le mode hardware qui permet un rendu plus rapide. Le doublebuffering consiste à ce que le système crée automatiquement en mémoire vidéo (celle de la carte) une copie de la surface principale (en mémoire vidéo elle aussi sinon cela n'a pas de sens). Vous avez plusieurs possibilités lorsque vous travaillez sur la surface principale (qui est aussi celle d'affichage). Premièrement vous réalisez toutes vos opérations directement dessus mais alors vous vous exposez à des clics à l'écran, puisque cette surface peut être réactualisée bien avant que votre travail soit fini. Ce qui va provoquer des effets indésirables à l'écran. La solution du doublebuffering est très intéressante dans ce contexte, en effet une fois activé, une surface copie de la surface principale est automatiquement créée en mémoire vidéo, cela a pour désavantage de ponctionner une partie de la mémoire vidéo et donc de diminuer les performances globales. Mais ce désavantage est minimal au vue du fait que d'un point de vue du code vous travaillez toujours sur la même surface mais au niveau du système il différencie les deux... ainsi les problèmes sus-cités disparaissent. Aujourd'hui le doublebuffering est supporté pratiquement par toutes les cartes graphiques, cependant cette méthode n'est pas toujours la plus performante. Le mode software est moins rapide quant au rendu puisque lorsque l'on désire afficher à l'écran la surface principale, celle-ci doit d'abord parcourir le système jusqu'à la carte vidéo. Ce qui peut représenter parfois une grande perte de temps. En revanche travailler sur une surface en mémoire système a aussi ses avantages, puisque les calculs se font bien plus rapidement qu'en mémoire vidéo. Dès lors l'on pourrait se dire qu'un "mix" des deux techniques est parfait. Et bien il n'en n'est rien, c'est même le contraire, car à ce moment-là les retours entre les deux types de mémoires sont trops fréquents et diminuent énormément les performances de votre application.

Maintenant que vous avez créé la surface principale (peu importe où) vous allez certainement manipuler d'autres surfaces : en chargeant des images, des surfaces tampons (buffer)... Pour cela vous utiliserez la fonction suivante :

 
SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);

Les quatre premiers arguments vous sont familiés maintenant puisque ce sont les mêmes que ceux de SDL_SetVideoMode(). En revanche les quatre derniers sont nouveaux. Ils correspondent aux valeurs RGBA que vous désirez donner à votre nouvelle surface. Soit vous donnerez des valeurs sur 32 bits à la main, soit vous préfèrerez partir des valeurs d'une surface déjà existance, comme suit :

 
SDL_Surface *surface;
 
surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
                               surface_pre_existante->w,
                               surface_pre_existante->h,
                               surface_pre_existante->format->BitsPerPixel,
                               surface_pre_existante->format->Rmask,
                               surface_pre_existante->format->Gmask,
                               surface_pre_existante->format->Bmask,
                               surface_pre_existante->format->Amask);
 
 

Enfin la fonction qui nous reste à étudier est la suivante :

 
void SDL_FreeSurface(SDL_Surface *surface);

Fonction qui libère les ressources liées à une surface. Voilà maintenant vous savez créer la surface principale, gérer les modes vidéo et créer des surfaces "à côté".


Travailler sur les surfaces

Travailler sur les surfaces consiste en peu d'étapes : - réaliser des transferts entre les surfaces que vous aurez créées - travailler sur les pixels d'une surface - effectuer le rendu à l'écran Au niveau de la première étape vous travaillerez essentiellement avec la fonction :

 
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);

Cette fonction prend quatre paramètres, deux surfaces et deux zones rectangulaires. Une valeur de chaque pour la surface source à transférer, et une valeur de chaque pour la surface de destination. Ainsi vous pouvez transférer toute une surface (en passant NULL) ou une zone précise de celle-ci. Veillez à bien vérifier la valeur de retour de la fonction, pour vérifier le transfert. Pour réalisez la seconde étape vous devrez d'abord bloquer la surface récursivement en écriture si elle se situe en mémoire vidéo, afin d'assurer à SDL un accès exclusif à la zone. Pour cela vous utiliserez les fonctions :

 
int SDL_LockSurface(SDL_Surface *surface);
void SDL_UnlockSurface(SDL_Surface *surface);

Bloquer et débloquer les surfaces placées en mémoire vidéo est indispensable lorsque vous travaillez directement sur les pixels de celles-ci. Vous risquez en ne le faisant pas qu'une autre application, ou que le WM travaille sur ce même pixel ou le bloque et alors vous auriez une belle erreur mémoire (segmentation fault bien connu en C). Une surface peut être bloquée plusieurs fois récursivements (depuis SDL 1.1.8). Voici comment appeler les fonctions :

 
if(SDL_MUSTLOCK(screen)){
        if(SDL_LockSurface(screen) < 0){
            /* on ne peut pas bloquer la surface */
        }
}
 
/* Travail sur les pixels */
 
if(SDL_MUSTLOCK(screen)){
        SDL_UnlockSurface(screen);
}

La dernière étape consiste en un appel à l'une des fonctions suivantes :

 
void SDL_UpdateRects(SDL_Surface *screen, int numrects, SDL_Rect *rects);
void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h);
int SDL_Flip(SDL_Surface *screen);
Remarque
SDL_Flip() n'est à utiliser que lors d'une application activant le doublebuffering.

Les deux premières fonctions vous permettent de mettre à jour une surface en une ou plusieurs zones de la surface, aussi bien que la zone complète. Ainsi, SDL_UpdateRects() prend la surface à mettre à jour, puis un nombre de zones à mettre à jour, et enfin la liste de ses zones. Ces zones sont des SDL_Rect :

 
typedef struct {
	Sint16 x, y;
	Uint16 w, h;
} SDL_Rect;

La fonction SDL_UpdateRect() vous permet de mettre à jour une unique zone de la surface, mais aussi toute la surface en passant 0 à chacun des paramètres.


Diverses fonctions sur les surfaces

SDL définit plusieurs fonctions annexes vous facilitant la vie dans bien des cas très simples. SDL vous permet : - de charger/sauver un BMP sous la forme d'une surface (via SDL_image vous pouvez charger d'autres formats comme le JPEG,le GIF,le PNG,le TIFF...) - connaître la couleur (composantes RGBA) d'un pixel d'une surface - remplir une surface d'une certaine couleur - convertir une surface en une autre (son pixelformat) - de décider de la palette de couleur courante - de créer une GammaRamp (si le matériel le supporte) - de rendre une couleur transparente sur une surface (alpha-blending) Le chargement et la sauvegarde d'un BMP se font par un appel à :

 
SDL_Surface *SDL_LoadBMP(const char *file);
int SDL_SaveBMP(SDL_Surface *surface, const char *file);

Pour connaître les composantes RGBA d'un pixel vous utiliserez l'une des fonctions ci-après :

 
void SDL_GetRGB(Uint32 pixel, SDL_PixelFormat *fmt, Uint8 *r, Uint8 *g, Uint8 *b);
void SDL_GetRGBA(Uint32 pixel, SDL_PixelFormat *fmt, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);

Remplir une surface se fait par l'utilisation de la fonction suivante :

 
int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);

Pour remplir le champs color, utilisez l'une des fonctions suivantes :

 
Uint32 SDL_MapRGB(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);
Uint32 SDL_MapRGBA(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b, Uint8 a);

Pour convertir une surface :

 
SDL_Surface *SDL_ConvertSurface(SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags);
/* Convertit dans le format d'affichage. 
    A appeler lorsque vous chargez en mémoire une image pour accélérer les transferts */
SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);

Pour remplir une palette d'une surface 8 bits :

 
int SDL_SetPalette(SDL_Surface *surface, int flags, SDL_Color *colors, int firstcolor, int ncolors);

Pour gérer les gammaramps :

 
int SDL_SetGamma(float redgamma, float greengamma, float bluegamma);
int SDL_SetGammaRamp(Uint16 *redtable, Uint16 *greentable, Uint16 *bluetable);
int SDL_GetGammaRamp(Uint16 *redtable, Uint16 *greentable, Uint16 *bluetable);

Enfin lorsque vous désirez qu'une couleur se trouve supprimée à l'affichage d'une surface (appeler cette fonction avant chaque nouveau transfert) :

 
/* flag est une combinaison binaire de SDL_SRCCOLORKEY et SDL_RLEACCEL
   key est la couleur à "effacer"
*/
int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key);

Voilà la présentation de la composante vidéo de SDL est enfin terminée, j'ai fait le tour des fonctions à une exception ou deux. Aussi veuillez vous reporter à la doc pour plus d'infos.

Outils personels