G r
a p h X
~~~~~~~~~~~~~~~~~~~~~~~
ßêta Version 1.1
by Thibaut Barthélemy
Pour toute question, remarque, idée d'optimisation,
remerciement, ou pour proposer une nouvelle fonction, n'hésitez
pas à poster sur le
forum d'AlinéaSofts, vous obtiendrez toujours une réponse
rapidement !
Si par hazard le forum était inaccessible, contactez-moi par
mail : barthib@hotmail.com
Si vous constatez une erreur dans la documentation ou dans la
bibliothèque, signalez-le moi ! n'oubliez pas que tous les
utilisateurs de GraphX profiteront de la correction.
GraphX est une bibliothèque qui se destine
surtout à la gestion de l'affichage dans des jeux demandant
beaucoup de rapidité.
Elle a pour principales qualités :
- la simplicité d'utilisation. Les débutants peuvent s'en
servir facilement ;
- la vitesse de ses fonctions. Elles sont très puissantes ;
- la taille de son code. En utilisant GraphX vous pouvez réduire
la taille de votre programme ;
Oui ! cette bibliothèque, qui est le fruit de plusieurs
semaines de travail minutieux, est entièrement programmée en
langage assembleur (sauf les fonctions de gestion des buffers qui
ne requièrent pas de vitesse), et les algorithmes ont été
optimisés à fond.
Au niveau de la vitesse d'exécution, les fonctions de GraphX se
veulent non loin de celles de GenLib (la fameuse bibliothèque
graphique de PpHd, auteur de SMA, Chrono Fantasy, ...) et donc
beaucoup plus performantes que celles d'ExtGraph (bibliothèque
graphique de la TICT, auteur de TIchess, FATengine, ...).
Si vous avez des questions à propos de cette bibliothèque,
il ne faut pas hésiter à demander de l'aide sur le forum d'AlinéaSofts,
vous obtiendrez toujours une réponse rapidement !
Si par hazard le forum était inaccessible, contactez-moi par
mail : barthib@hotmail.com
Dans la barre de menu de l'IDE de TIGCC, il y a un menu "Project".
Cliquez dessus puis sélectionnez l'item "Add files..."
qui s'y trouve.
Dans la boîte de dialogue qui s'ouvre, sélectionnez les deux
fichiers graphx.a et graphx.h qui sont situés
dans le répertoire C:\GraphX\fichiers de votre ordinateur (à
moins que vous ayez demandé au programme d'installation de
placer la bibliothèque autre part).
C'est tout !
Il vous faudra bien évidemment écrire #include "graphx.h"
au début de votre programme.
Il est livré avec GraphX un excellent
programme permettant de générer des sprites au format de
GraphX, il s'agit de TI Paint Plus (programmé par
Olivier Martin), qui est accessible dans le groupe GraphX
de votre menu Démarrer (il vous faut avoir installé la
bibliothèque sur votre PC pour qu'il soit accessible, consultez le fichier
A_LIRE.htm pour plus de détails).
N'oubliez pas de sélectionner des dimensions valides pour le
sprite ("Taille de l'image" : 8x8, 16x16, 32x32) et de
choisir le format (type) "Sprite GraphX (langage C)" à
l'enregistrement.
Bien malheureusement la génération des sprites masqués ne
fonctionne pas encore, vous devrez soit générer un sprite
"normal" et ajouter le masque "à la main",
soit faire appel à un autre utilitaire compatible, par exemple
celui de Xlib (c'est une bibliothèque graphique, programmée
par Julien Sabatier).
Copiez le code ci-dessous dans un nouveau projet de TIGCC, et
créez 2 sprites (un sprite non masqué de dimensions 8x8 nommé s_8x8n,
et un sprite masqué de dimensions 32x32 nommé s_32x32m)
que vous placerez dans un fichier nommé sprites.h.
Ajoutez le fichier de sprites à votre projet en allant dans le
menu Project|Add files...
Ensuite, grâce au même menu, ajoutez à votre projet les deux
fichiers graphx.a et graphx.h qui sont situés
dans le répertoire C:\GraphX\fichiers de votre ordinateur (à
moins que vous ayez demandé au programme d'installation de
placer la bibliothèque autre part).
Une explication détaillée de chaque fonction est accessible par
le menu situé à gauche de cette page.
/* si on utilise les fonctions de lecture du clavier (tel que GX_RIGHTpressed) on ne peut pas compiler le programme pour TI89 et TI92+/TIV200 simultanément car la matrice du clavier est différente entre ces 2 modèles (on considère deux modèles (89 vs 92+ & V200) car la TI V200 est identique à la TI 92+ au niveau du hardware). */ //#define USE_TI89 // compile pour TI 89 #define USE_TI92PLUS // compile pour TI 92+ #define USE_V200 // ... et TI V200 #define OPTIMIZE_ROM_CALLS // optimisation de l'appel aux ROM-calls #define MIN_AMS 100 // compile pour l'AMS 1.00 ou plus #define SAVE_SCREEN // sauvegarde et restaure l'écran tel qu'il était avant l'exécution du programme #include <tigcclib.h> // inclut tous les fichiers d'en-tête de la bibliothèque de TIGCC #include "graphx.h" // on affiche avec GraphX :) #include "sprites.h" // fichier contenant les 2 sprites boolean Afficher_Le_Fond= FALSE; // variable qui va servir à savoir si l'on doit afficher le motif de fond ou laisser l'arrière plan vierge void Faire_Clignoter_Le_Fond(void) // fonction qui va être appelée automatiquement 2 fois par seconde { Afficher_Le_Fond= !Afficher_Le_Fond; // inversion de la valeur booléenne (TRUE devient FALSE et inversement) } void _main(void) // fonction principale { GX_HANDLE workHdl, fondHdl; // handles des 2 buffers que l'on va utiliser short x, y; if (!GX_PowerOn(TRUE)) // mise en route :) on passe TRUE car on va utiliser les fonctions de lecture du clavier (GX_rowread et ses dérivés) return; // si l'initialisation a échouée, on quitte :( workHdl= GX_CreateBuffer(); // création d'un buffer if (workHdl == H_NULL) // il n'y a pas assez de mémoire pour le créer ? { GX_PowerOff(TRUE); // arrêt de GraphX :( return; } fondHdl= GX_CreateBuffer(); // création d'un buffer if (fondHdl == H_NULL) // il n'y a pas assez de mémoire pour le créer ? { GX_PowerOff(TRUE); // GX_PowerOff se charge de libérer la mémoire prise par le buffer workHdl car on lui passe TRUE en paramètre return; } GX_SetWorkBuffer(fondHdl); // à partir de maintenant, les fonctions vont afficher dans le buffer fondHdl for (y=0; y<127; y+=8) for (x=0; x<239; x+=8) GX_PutSprite08_ER(s_8x8n, x, y); // création de l'arrière-plan GX_CreateTimer(Faire_Clignoter_Le_Fond, 2); // la fonction Faire_Clignoter_Le_Fond est désormais appelée automatiquement par GraphX, 2 fois par seconde ! // les timers sont libérés systématiquement par GX_PowerOff. L'utilisation de GX_DestroyTimer n'est donc pas obligatoire GX_SetWorkBuffer(workHdl); // à partir de maintenant, les fonctions vont afficher dans le buffer workHdl x= y= 40; do { GX_joypad etat_des_touches; // variable recevant l'état des touches de direction etat_des_touches= GX_ReadJoypad(); // lecture des touches de direction if (etat_des_touches.left) x--; else if (etat_des_touches.right) x++; else if (etat_des_touches.up) y--; else if (etat_des_touches.down) y++; if (Afficher_Le_Fond) // s'il faut afficher le motif de fond... GX_Copy(fondHdl); // on copie l'image de fond (arrière-plan) dans le buffer de travail... else // sinon... GX_Clear(); // on laisse vierge l'arrière-plan GX_PutSprite32_MS(s_32x32m, x, y); // affichage dans le buffer de travail d'un sprite masqué GX_DisplayWorkBuffer(); // affichage du buffer de travail à l'écran :) } while (!GX_ESCAPEpressed()); // bouclage tant que la touche "escape" n'est pas pressée GX_PowerOff(TRUE); // comme on passe TRUE à GX_PowerOff, GraphX désalloue automatiquement tous les buffers qu'on a créé }
Pour simplifier la vie au programmeur qui
utilise GraphX !
L'affichage utilise une technique que j'ai appelé "triple-swap-buffering"
qui est extrêmement rapide et consiste en un échange de
pointeurs, donc si les buffers étaient identifiés par leurs
adresses, celles-ci deviendraient invalides après chaque
affichage du buffer de travail... imaginez le b***el !
Ils sont très différents des handles de l'AMS.
N'essayez jamais de libérer un buffer avec les fonctions HeapFree
et ses dérivés, vous planterez à coup sûr votre calculatrice
!
Pour résumer, le handle d'un buffer est l'index des données décrivant
le buffer, dans une table interne à GraphX. Rassurez-vous, vous
n'avez pas du tout besoin de comprendre ça pour utiliser la
bibliothèque.
L'affichage utilise une technique que j'ai
appelé "triple-swap-buffering" qui est extrêmement
rapide et consiste en un échange de pointeurs. En fait, après
l'appel à la fonction GX_DisplayWorkBuffer le buffer de
travail contient ce qui était affiché à l'écran avant, ce
peut être une ancienne image "datant" de plusieurs
affichages, ou juste l'image précédente. On ne peut pas le
savoir !
La meilleure façon de ne pas s'ennuyer avec cette caractéristique
est de redessiner entièrement le buffer de travail après
l'affichage.
Sur les calculatrices de dernière génération (dites "HW2")
l'image peut manquer de fluidité lorsque ses éléments (sprites,
etc) se déplacent vraiment très rapidement. Ceci est dû aux
particularités du LCD des calculatrices HW2 qui compliquent le
processus de synchronisation de GraphX ("triple-swap-buffering").
Heureusement ce défaut de fluidité disparaît totalement
lorsque :
- soit le nombre de sprites affichés augmente ;
- soit vous effectuez d'autres opérations, par exemple un
scrolling ;
- soit vous ralentissez un peu votre programme ;
En résumé, tout s'arrange lorsque GraphX a le temps de réaliser
plus d'opérations entre deux rafraîchissement de l'affichage.
Il y a deux origines possibles à ce bug :
a) | vous détectez les touches avec les fonctions de bas
niveau de GraphX (comme GX_ReadJoypad, GX_rowread,
GX_ALPHApressed, etc) mais vous n'avez
pas désactivé les interruptions matérielles numéros 1
et 5. Tout va rentrer dans l'ordre si vous les désactivez. Vous pouvez demander à GraphX de le faire lui-même (c'est plus simple pour vous) en passant TRUE à la fonction de mise en route (GX_PowerOn). |
b) | vous détectez les touches avec les fonctions de bas niveau de TIGCC (_rowread, etc). Pour que tout rentre dans l'ordre, remplacez-les par l'équivalent de GraphX : GX_rowread. Cette fonction s'utilise exactement de la même façon que le _rowread de TIGCC, donc il vous suffit de rajouter les 2 lettres "GX" devant chaque "_rowread" ! |
Sachez que vous devriez remplacer vos GX_rowread par les macros prédéfinies correspondantes (GX_ALPHApressed, GX_F1pressed, GX_ESCAPEpressed, etc), votre code gagnera en lisibilité et en portabilité, car ces macros s'adaptent automatiquement à la plateforme pour laquelle vous compilez (TI89 ou TI92+/V200).
La plupart du temps, c'est le nombre variable de sprites à afficher qui provoque ce défaut de régularité. Aux moments où vous devez afficher beaucoup de sprites, le rythme du programme ralentit, et inversement lorsque le nombre de sprites est moindre.
Pour régulariser la fréquence d'affichage, commencez par
choisir le temps devant s'écouler entre deux affichages. Disons
2 centi-secondes.
Nous allons maintenant ajouter deux lignes autour de l'endroit où
vous affichez le buffer de travail à l'écran :
- juste avant l'appel de la fonction GX_DisplayWorkBuffer,
écrivez :
while (GX_CountDown != 0) /*on régularise*/ ; //n'oubliez pas le point-virgule
- juste après l'appel de la fonction GX_DisplayWorkBuffer, initialisez GX_CountDown avec la période d'affichage :
GX_CountDown= 2; //centièmes de seconde qui doivent s'écouler impérativement entre 2 affichages
Désormais il se passera toujours très précisément 2 centièmes
de seconde entre deux affichages, quel que soit le nombre de
sprites !
Si vous ne constatez pas d'amélioration, augmentez la période.
Si, au contraire, le jeu est devenu bien régulier mais trop
lent, vous pouvez la diminuer !
Pour en savoir plus au sujet de GX_CountDown, consultez
sa documentation (accessible par le menu situé à gauche de
cette page).
On ne peut gère choisir qu'entre 1 cs, 2 cs, 3 cs et 4 cs
comme période d'affichage, car au-delà de 4 cs le jeu
deviendrait trop lent.
Pour fixer plus librement la période, vous pouvez vous concevoir
votre propre variable auto-décrémentée avec un timer (prenez
bien soin de déclarer la variable auto-décrémentée en tant
que volatile, regardez dans graphx.h la déclaration
de GX_CountDown si vous ne voyez pas comment faire).
Vous aurez alors un choix beaucoup plus fin de la période (précision
de 1/256ème de seconde, contre 1/100ème de seconde avec la méthode
présentée ci-dessus).
Consultez la documentation de la fonction GX_CreateTimer
pour plus d'informations sur les timers.
La réponse dépend de la quantité de sprites qui composent votre image et du sens du scrolling.
défilement horizontal | si le buffer est remplit de sprites à moitié ou plus | si le buffer contient plus de "blancs" que de sprites |
Utilisez GX_HorizontalScroll. Prenez note de la remarque formulée sous le tableau. | Effacez avec GX_Clear puis réaffichez les sprites à leur nouvelle position. |
défilement vertical | si le buffer est remplit de sprites au moins au 1/3 | si le buffer contient peu de sprites |
Utilisez GX_VerticalScroll. Prenez note de la remarque formulée sous le tableau. | Effacez avec GX_Clear puis réaffichez les sprites à leur nouvelle position. |
Remarque :
Pour scroller avec les fonctions dédiées (GX_VerticalScroll
ou GX_HorizontalScroll), vous devez garder l'image après
son affichage afin de pouvoir y appliquer à nouveau un défilement
(pour l'affichage suivant)... Or nous savons que l'affichage du
buffer de travail détruit son contenu ! Il est donc nécessaire
d'allouer un second buffer (que nous nommons tempBuffer,
par exemple) et de travailler de cette manière :
- | lorsque vous avez appliqué le scroll sur le buffer de travail, changez de buffer de travail (avec GX_SetWorkBuffer) pour désormais travailler dans le second buffer (tempBuffer dans notre exmple). |
- | avec GX_Copy, copiez l'ancien buffer de travail (celui que nous avons scrollé) dans le nouveau. Si certains éléments doivent avoir une trajectoire ou une vitesse différentes du reste de l'image, ne les dessinez pas dans le buffer de travail originel mais dans le nouveau, après la copie. |
- | affichez avec GX_DisplayWorkBuffer. |
- | reprenez l'ancien buffer de travail (avec GX_SetWorkBuffer). Son contenu n'a pas changé puisque ce n'est pas lui qui a été affiché ! |
Oui ! vous pouvez exploiter ces fonctions
performantes dans des programmes dont l'affichage n'est pas géré
par GraphX. Elles sont indépendantes du reste de la bibliothèque.
Attention : pour détecter correctement les touches, les
fonctions de lecture du clavier exigent que les interruptions matérielles
numéros 1 et 5 soient désactivées.
Mais vous pouvez aussi, bien sûr, les utiliser dans un programme
dont l'affichage est géré par GraphX.
Pour cela il vous suffit de connaître l'adresse des plans clair et foncé. GraphX met à votre disposition la fonction GX_GetDarkPlane pour obtenir l'adresse du plan foncé d'un buffer, et GX_GetLightPlane pour obtenir l'adresse du plan clair d'un buffer. C'est ainsi que vous pourrez entre autres écrire avec GX_DrawStr (cette fonction est indépendante de GraphX). Par exemple, pour écrire "Bonjour" en gris foncé, en haut de l'écran, dans le buffer identifié par MonBuffer, il faut procéder de cette manière :
void *plan_fonce; // déclaraction du pointeur vers le plan foncé du buffer plan_fonce= GX_GetDarkPlane(MonBuffer); // obtention de l'adresse du plan foncé du buffer GX_DrawStr(plan_fonce, 0, 0, "Bonjour", FALSE); // écriture
Oui, en respectant la convention de passage du C standard,
donc en empilant les paramètres dans l'ordre inverse de leur déclaration.
La convention du langage C sur TI 89/92+/V200 impose aussi que :
- les fonctions retournant un pointeur le placent dans a0,
celles retournant une valeur la placent dans d0.
- les fonctions détruisent les registres a0, a1,
d0, d1, d2, donc sauvegardez-les si
vous y stockez des valeurs importantes.
Enfin, sachez que certaines "fonctions" de GraphX, bien
que documentées comme des fonctions, sont des macros ! Consultez
graphx.h pour savoir lesquelles sont concernées. Vous
devrez convertir ces macros C en leur équivalent ASM.