Cet article fait suite au précédent. J'explique ici comment fonctionne le système des tuiles afin d'économiser la mémoire vidéo.
Le système des tuiles était très utilisé dans les années 80 alors que les ordinateurs ne possédaient que quelqes Ko de mémoire RAM. Une quantité insuffisante pour conserver un bitmap complet de l'affichage vidéo. Dans cette chronique j'utilise un générateur vidéo VGA 16 couleurs d'une résolution de 640x480. Ce qui nécessiterais 153600 octets de RAM pour conserver un bitmap complet de l'affichage vidéo. Par contre en utilisant une table de tuiles de 8x8 pixels et un buffer vidéo contenant seulement des index de type unsigned char, la table peut contenir 256 tuiles différentes. La mémoire vidéo utilise donc 640/8*480/8=4800 octets par buffer. Il y a 2 buffer vidéo, video_buffer et working_buffer. Si on utilise une table de tuiles complète (256 tuiles) on a besoin de 32x356=8192 octets supplémentaire. Donc au total 17792 octets sont utilisés par le générateur vidéo sur les 64Ko disponibles. Énorme gain par rapport à 2 buffers de type bitmaps: 307200 octets.
Ce court vidéo montre une animation de 8 balles qui rebondissent sur la bande blanche qui entoure l'écran ainsi que l'une contre l'autre. Chaque balle d'une couleur utilise 4 tuiles.
Système de tuiles
Le système de tuiles est en fait très simple c'est le principe des polices de caractères à points fixes, mais plutôt que de contenir des caractères, la table contient (elle peut contenir aussi des caractères) des figures réparties sur plusieurs tuiles. Il suffit d'afficher ces groupes de tuiles dans l'ordre approprié pour former la figure désirée. Si on prend l'exemple du démo ci-haut. Les balles sont de 16x16 pixels. Puisque chaque tuile ne contient que 8x8 pixels, il faut 4 tuiles pour dessiner chaque balle. La mémoire vidéo est organisée comme une mémoire de terminal texte, elle ne contient de le numéro des tuiles et le générateur vidéo utilise ces numéros pour indexer la table des tuiles qui elle contient les pixels à afficher. Utilisé comme terminal texte ce système pourrait afficher 60 lignes de 80 caractères 8x8 mais tous les caractères doivent-être de la même couleur et le fond uniforme de couleur unique. Cependant on pourrait modifier facilement le générateur vidéo pour avoir des caractères en inverse vidéo.
représentation d'objets graphiques
Pour afficher des objets graphiques il faut créer des groupes de tuiles qui agencés ensemble dans le buffer vidéo formerons l'objet désiré.
Par exemple, la balle rouge est construite avec les tuiles {16,17,18,19}. Pour dessiner une balle rouge à l'écran à la position {4,20} on place dans video_buffer l'information suivante:
Pour former l'image d'une balle il faut que l'ordre et la position relative des tuiles soit respecter. Donc si on veut déplacer la balle vers la droite de 10 positions et de 5 position vers le bas, on ajoute 10 à la coordonnée x et 5 à la coordonnée y de chacune des tuiles du groupe.
video_buffer[4][20]=16; // ligne 4, colonne 20
video_buffer[4][21]=17; // ligne 4, colonne 21
video_buffer[5][20]=18; // ligne 5, colonne 20
video_buffer[5][21]=19; // ligne 5, colonne 21
On sauve énormément de mémoire avec ce système mais il comporte aussi de nombreuses limitations. D'abord il faut utiliser un fond d'écran uniforme. Dans le démo le fond est jaune pâle et donc les pixels des tuiles qui entourent le dessin des balles doivent-être de la même couleur sinon on voit un rectangle autour des balles. Si le fond d'écran n'était pas uniforme il faudrait des tuiles différentes pour chaque couleur de fond.
Le deuxième problème est qu'on ne peut couvrir la surface complète avec 256 tuiles, il y a donc des répétitions. On le constate dans les jeux vidéo du début des années 80 alors que les fonds d'écrans sont fait de tuiles qui se répètent.
Si vous observez attentivement les jeux vidéo de ces années vous remarquerez que les parties de l'écran ou se déplace les objets sont de couleur uniforme. Les corridors du labyrinthe ou se déplace PACMAN est de couleur uniforme et tous les objets qui apparaissent dans le labyrinthe sont construits avec des tuiles dont le fond est de même couleur.
Il est possible de modifier dynamiquement le contenu d'une tuile pour modifier les pixels de fond en fonction de la position de l'objet sur l'écran mais ces objets doivent n'avoir qu'une seule instance sinon chaque instance doit utiliser son propre groupe de tuiles. Comme dans ce démo ou il y a 4 tuiles pour chaque couleur de balle. Si on voulait créer 1 balle de chacune des 16 couleurs disponible il faudrait un total de 64 tuiles. Il ne faut pas oublier qu'on ne peut créer qu'un nombre limité de tuiles. Si l'index est de type unsigned char comme dans ce démo on est limité à 256 tuiles. Si on augmente trop le nombres de tuiles on perd tout avantage en terme de gain mémoire.
code source
le générateur vidéo a été créé comme un projet module nommé module-vga16t afin d'être facilement réutilisable dans d'autre applications. Il s'agit d'une première version qui sera vraisemblablement modifiée.
fichier d'entête vga16t_gen.h
fichier vga16t_gen.xc
Fichier d'entête tuiles.h
Fichier tuiles.xc
Fichier bouncing-balls.xc
640pixels en 25,6µsec
l'intervalle de temps disponible en VGA pour sortir les 640 pixels est de 25,6µsec. Même avec un core logique clocké à 100Mhz il ne serait pas possible de faire ça en 'C' si ce n'était d'une fonctionnalité des xcore, c'est à dire le buffered port. Voici le code qui sort les pixels.
Cette boucle for sort 1 ligne de 640 pixels. video_buffer contient les index des tuiles comme expiqué ci-haut. Ces index sont utilisés pour aller chercher l'informationn des pixels à afficher dans la table des tuiles. La table est organisé en 8 entiers de 32 bits par tuile. Chaque entier correspond à un segment de ligne de 8 pixels. En effet puisqu'on a besoin de 4 bits par pixel 32 bits peuvent contenir 8 pixels. La variable r indique quelle ligne de la tuile utiliser en correspondance avec la ligne vidéo courante.
line_cntr est le numéro de ligne du frame vidéo (0-524). Puisque l'affichage commence à la ligne 23 de chaque frame on soustrait 23 pour avoir la bonne valeur de ligne dans buffer_video. Et puisque qu'une tuile s'étend sur 8 lignes on obtient r par (line_cntr-23) % 8. rgb est le port 4 bits qui envoie l'information pixel au moniteur. Comme vous le voyez on envoie l'entier au complet dans rgb car on a défini un buffered port de 32 bits pour rgb ainsi qu'un signal clock de 25Mhz.
int y = (line_cntr-23)/8;
int r = (line_cntr-23) % 8;
for (int x=0;x<SCREEN_WIDTH;x++){
int _8pixels=tuiles[video_buffer[y][x]][r];
rgb <: _8pixels;
}//for
Dans le fichier bouncing-balls.xc juste avant la fonction main on a les lignes:
rgb est donc buffered sur 32 bits et on défini un signal d'horloge appellé pixclk.
out buffered port:32 rgb = XS1_PORT_4C; // J7-5:J7-8
clock pixclk= XS1_CLKBLK_1;
À l'intérieur de main() on a les lignes suivantes:
Pour le startKIT XS1_TIMER_MHZ vaut 100. 4 est un diviseur, donc le clock rate est de 100/4=25Mhz. On associe ensuite ce signal d'horloge au port rgb et finalement on démarre le signal. A partir de ce moment chaque fois qu'on envoie un entier de 32 bits vers rgb est il découpé en tranches de 4 bits à intervalle de 40 nsec sans que le core logique est besoin d'intervenir. Pendant ce temps la boucle for poursuit son exécution. Il n'y a donc aucun délais entre la envoie de chaque _8pixels.
configure_clock_rate(pixclk,XS1_TIMER_MHZ,4);
configure_out_port(rgb,pixclk,0);
start_clock(pixclk);
Notez que les bits de poids faibles sortent en premier donc les bitmaps des tuiles est inversé comme dans un miroir (gauche-droite).
lien vers les sources des 2 projets:
projet bouncing-ball
projet module-vga16t
Aucun commentaire:
Publier un commentaire
Remarque : Seuls les membres de ce blogue sont autorisés à publier des commentaires.