Tuesday, September 29, 2009

IRQ_VCOUNT

Voilà exactement le genre de technique de programmation que j'ai toujours admiré sur les consoles (et les ordinateurs Commodore, qui d'une certaine façon, sont des consoles avec un clavier et un lecteur de disquette). Mettons que je veuille modifier au milieu d'une image les paramètres de l'affichage, comme la palette de couleur, la priorité et la position de tel ou tel plan du jeu, etc.

Pour un jeu vidéo, ça me permettrait par exemple de garder une zone de score fixe dans un jeu à scrolling, ou (*ze* cas d'école) de virer la palette dans les bleus à la hauteur du plan d'eau. Dans mon éditeur de niveau, ça permettrait aux boutons de rester affichés correctement malgré les choix d'afficher ou de masquer les différents "calques".

Bien sûr, ça peut être approché aussi sur les cartes VGA etc. à condition de bien synchroniser le timer avec les débuts et fin de ligne à l'écran. Et c'est là toute l'élégance de la "solution console": il y a une ligne d'interruption dédiée directement à cette synchronisation (donc plus besoin de timers), et dans le cas de la DS, on a carrément le luxe d'un registre supplémentaire qui indique si la ligne courante correspond ou non à une ligne indiquée.

void vcountirq() {

if (REG_VCOUNT<192) {
BG_PALETTE[0]=RGB15(0,0,31);
REG_DISPSTAT = (REG_DISPSTAT&0xff)|(192<<8);
} else {
BG_PALETTE[0]=RGB15(0,0,0);
REG_DISPSTAT = (REG_DISPSTAT&0xff)|(160<<8);
}
The 'vertical count matched' interrupt is a perfect explanation of why I prefer underground console programming against overworld PC games. It is a very simple mechanism that lets code be run when the graphic controller is about to process line Y, and yet it enables a wide panel of special effects in games. The most common "showcase" is for sure implementing water: at a given horizontal position, you swap the palette and provide a "bluer" one so that bottom of the screen looks sunken.
The above code snippet does just that in a "raw" fashion where only backdrop color is changed. Don't let yourself scared by those bit masking, combining and shifting. It is just another facet of computing that is *very* handy when dealing with hardware registers. If you think of your process as an "electronic process" taking place on an array of bits, it just becomes natural.

Une petite fonction toute simple donc, qui sera appelée "sur la bonne ligne" et qui change les couleurs (ici, une seule, et entre deux valeurs pré-calculées, mais c'est juste pour la démo). Astuce, pour pouvoir être invoquée sur plusieurs lignes (plutôt que toujours sur la même) et donc remettre la couleur à sa valeur précédente, je change le numéro de la ligne où l'interruption doit se produire pour lui donner la valeur suivante dans l'interruption elle même (c'est le code avec REG_DISPSTAT).

Bon, okay, le code avec les & et les << fait un peu peur: c'est des manipulations de portions d'entiers pour modifier un byte qui se trouve en réalité dans la moitié d'un registre de 16 bits. La programmation DS est remplie de bidouille de ce genre. C'est de l'informatique d'avant XML, qui exploite pleinement la structure binaire des nombres.

Bien sûr, pour que ça fonctionne, je dois enregistrer la fonction vcountirq via la libnds (il ne suffit pas de lui donner le bon nom pour que ça se fasse tout seul. Vous rêvez, les gars!)
irqSet(IRQ_VCOUNT, vcountirq);

REG_DISPSTAT = REG_DISPSTAT|(160<<8);
irqEnable(IRQ_VCOUNT);
Vous remarquerez qu'on retrouve ici aussi les "manipulations bizarres" sur DISPSTAT, pour l'initialisation. Heureusement, le web est plein de gens qui passent leur temps à expliquer "X&FF = garder les 8 bits de poids faibles de X", etc.

Et pour désactiver l'effet,
irqDisable(IRQ_VCOUNT);
tout simplement.
J'aurais pu vous bricoler tout ça juste pour le fun ou parce que je prépare en secret un nouveau niveau de Bilou avec de l'eau. Malheureusement, je n'en suis pas encore là. Je suis dans une impasse avec mon éditeur de niveau, tout simplement: les boutons qui me permettent de décider si je veux voir ou non le fond, le plan principal, les sprites, etc. disparaissent ou deviennent illisibles par suite des manipulations pour changer la visibilité des plans. Résultat, rien de tel qu'un bon "virq" pour forcer l'affichage à "revenir" dans le bon état le temps de m'afficher la barre d'outils qui doit se trouver en bas de l'écran tactile...

Well, i wish i was posting this message because i'm working on an impressive water level. Not yet. I'm simply stuck with the UI of my level editor, where i need more layers than the console actually provide, so I'm doing a dynamic "horizontal split" when the user press "down" to access layer visibility setup. The hardest part is to integrate this properly in the GuiEngine (as usual with OOP >_<)

... Reste à intégrer ça dans le GuiEngine ...

edit: i managed to integrate and test on real hardware. It works, despite the amount of time taken by the operations confirms just "dma'ing" a new palette wouldn't work. The actual code for my 'vcount' handler is only 8 lines long (50 instructions, involving 8 stores to video registers), but it takes two scanlines to be fully processed. Plus, the first scanline shows a "flickering" area of some 24 pixels wide, suggesting that even the "companion code" that brings us from the interrupt to the "virtual void online" snippet is also too long. Maybe pushing it to ITCM would help ... and having the interrupt taking place one line in advance and forcing a busy loop until we encounter a HBLANK.

update: Pour la petite info, c'est maintenant intégré et testé sur la DS elle-même. Et ça marche, même si je suis surpris de voir 8 modifications de registres (les données sont constantes: ça se voit dans l'assembleur généré par le compilo) prendre quand même deux lignes d'affichage pour s'exécuter ! Je devrais peut-être essayer de mettre les routines concernées en ITCM, histoire de m'assurer qu'elles soient toujours en cache, ou qqch comme ça (sur du C++, ça va être coton, tiens!).

1 comment:

sverx said...

ARM7 default code is (was?) using VCOUNT IRQ (for touch screen handling, AFAIR), and I remember once I was using VCOUNT IRQ on ARM9 with no problems... so I'm sure you can set two "LYC" values, one for each processor.
http://nocash.emubase.de/gbatek.htm#lcdiointerruptsandstatus