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.

 

Frequently Asked Questions :

 

Informations générales Quels sont les avantages de GraphX ?

GraphX est-elle rapide ?

-- --
Aide pour débuter Où puis-je trouver de l'aide si j'ai des difficultés avec GraphX ?

Comment utiliser GraphX dans un projet avec TIGCC ?

Comment créer un sprite ?

Pourrais-je avoir un exemple complet de programme utilisant GraphX ?

-- --
Questions techniques Pourquoi les buffers sont-ils identifiés par des handles ?

A quoi correspondent ces handles ?

Pourquoi le contenu du buffer de travail est-il modifié après son affichage ?

-- --
Problèmes souvent rencontrés J'ai l'impression que l'affichage de mon programme est saccadé !

Mon programme semble détecter des touches qui ne sont pas pressées !

Mon jeu a une vitesse irrégulière. Comment régler ce défaut ?

Quel est le moyen le plus rapide de faire défiler ("scroller") une image ?

-- --
Utilisations spéciales de GraphX Puis-je utiliser GX_DrawChar, GX_DrawStr, ou toutes les fonctions de lecture du clavier comme GX_ALPHApressed, sans initialiser GraphX ?

Comment dessiner avec des fonctions d'autres bibliothèques que GraphX ?

Est-il possible de programmer en assembleur avec GraphX ?

 

 

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.