Revenir au plan du site
Utiliser la pile pour effacer, écrire ou lire des données
Sur tous les processeurs pouvant changer la pile de place, il est courant de détourner son usage premier pour profiter de la rapidité de lecture et d'écriture.
En effet, sur CPC, le POP ne coûte que 3 NOPS et le PUSH ne coûte que 4 NOPS. C'est extrêmement rapide. Concernant le POP, les 3 NOPS sont bien utilisés : Lire l'opcode et lire les deux octets
Pour un équivalent du POP en code classique avec des incréments 8 bits sur des données alignées, on est deux fois plus lent :
ld e,(hl) ; 2
inc l ; 1
ld d,(hl) ; 2
inc l ; 1
|
La pile est deux fois plus rapide, s'affranchit de toute contrainte d'alignement et de changement de poids fort.
Pour le PUSH, le gain est moins significatif (4 NOPS au lieu de 6 NOPS) mais sur de grosses quantités de données uniformes, c'est toujours 30%+rapide, ou le classique est 50%+lent selon le point de vue.
Le mythe des interruptions à couper
Hey! Comment ça, tu veux dire qu'on n'est pas obligé de couper les interruptions pour utiliser la pile de façon détournée?
Si l'usage propre veuille qu'on coupe les interruptions pour éviter des effets secondaires, ce n'est pas obligatoire dans la majorité des cas. C'est même l'exception!
Évidemment, cela demande un peu de préparation, comme savoir qui est susceptible d'interrompre et quelle taille de pile sera utilisée. Comme c'est votre code, vous êtes supposé le savoir! Si vous utilisez un player de musique ou que vous ne savez pas, contrôlez l'occupation max de la pile dans un espace vierge avec le débogueur. Une fois que vous savez (mettons 10 octets), vous avez votre marge à respecter et ensuite, la puissance peut s'exprimer.
Premier exemple, effacer l'écran :
fastClear
ld (.recup+1),sp
ld hl,0 : ld b,128
ld sp,hl
.raz
repeat 64 : push hl : rend
djnz .raz
.recup ld sp,#1234
ret
|
À première vue, il serait possible de répéter 8192 PUSH pour effacer 16384 octets. Seul problème, si une interruption a lieu juste avant la fin, on pourrait écraser la zone qui précède. Si dans votre cas c'est sans conséquence, c'est le meilleur choix. Mais là, je suis parti sur faire propre et conserver les 10 octets qui précèdent coûte que coûte. Je vois que 132*62*2 font 16368. Il restera 16 octets à effacer à la main. Voici le code propre.
fastClear
ld (.recup+1),sp
ld hl,0 : ld b,132
ld sp,hl
.raz
repeat 62 : push hl : rend
djnz .raz
.recup ld sp,#1234
ld hl,#C000+15 : xor a
.finish ld (hl),a : dec l : jr nz,.finish
ret
|
Si une interruption intervient, elle va écrire des données en empilant son adresse de retour, ses registres modifiés. Mais une fois ces registres dépilés et le retour effectué, notre routine va reprendre l'effacement, en repassant par dessus. Comme nous avons pris notre marge (si vous voulez tester ça en Basic, il faudra plus que 10 octets, je vous laisse calculer), le pointeur de pile est remis à sa bonne valeur et l'effacement final se fera sans différence notable rapport à la version bourrine.
Il va de soit que l'usage de la pile est intéressant pour des quantités de mémoire significatives. On n'utilisera pas la pile pour 4 octets.
Dans le jeu Dragon Ninja, le décor est affiché à la pile car les motifs sont assez répétitifs pour tenir dans tous les registres, le code est très fûté (mais ils coupent les interruptions, bouuuuuh!)
La lecture à usage unique
Nous sommes dans un cas pratique très courant du jeu vidéo. Lors de l'affichage de nos sprites, nous avons empilé (interruptions actives ^_^) dans un buffer bien dimensionné, les adresses d'écriture de nos sprites, ainsi que le pixel du fond écran, afin de pouvoir le restituer après le flip du buffer suivant. Notre routine de sprite est optimisée (du code généré), la liste contient des triplets 16 bits. Deux octets à restituer et deux adresses pour la restitution. Les routines savent combien d'octets elles ont à restituer donc le code de restitution est trivial (on peut dérouler si on veut).
lectureTriplets
ld b,17 ; on a dans ce cas d'école 17 triplets à lire
ld (.recup+1),sp
ld sp,restitution
.boucle
pop de
pop hl : ld (hl),e
pop hl : ld (hl),d
djnz .boucle
.recup ld sp,#1234
ret
|
Si une interruption intervient, seules les données déjà lues sont écrasées. Comme elles sont à usage unique, aucun impact sur notre code qui peut être interrompu à n'importe quel moment.
La lecture pure
C'est le seul cas qui peut éventuellement être pénible à gérer. Mais comme on l'a vu, la lecture est deux fois plus rapide, c'est très intéressant. Que faire?
Couper les interruptions comme un lâche?
Méthode n°1, si vous n'en avez pas besoin, bien sûr qu'il faut les couper pour toujours, libérez la puissance de votre CPU!
Méthode n°2, simple et évidente, mettez votre routine dans l'interruption! Comme ça, l'interruption ne peut pas vous embêter ^_^
Méthode n°3, découper votre code pour que le temps d'exécution n'excède pas 52x64=3228 NOPS, ce qui correspond déjà à la louche, à l'affichage d'un sprite carré de 8 octets sur 32 lignes (c'est gros). Selon que vous ayiez un besoin impératif de placement de l'interruption, il faudra attendre la première, ensuite vous pouvez enquiller les routines, vous serez synchronisé. Si vous n'avez pas besoin du placement exact, lancez vos routines sans plus attendre en coupant les interruptions, vous les remttez à la fin. Même si vous débordez la requête d'interruption ne sera pas perdue et s'activera dès le EI. Notez que le DI:EI coûte seulement 2 NOPS, inutile de s'en priver, à fortiori pour la mise au point.
Vous avez besoin des interruptions mais vous voulez impérativement que votre multimode se déclenche à la bonne ligne pour éviter la séparation entre votre écran de jeu et le HUD ?
Méthode n°4, synchronisez-vous une fois pour toutes sur la dernière interruption (celle de l'écran non visible) et intégrez un compteur à votre interruption (ou une liste chainée tournante). Et déclenchez vos affichages juste après l'interruption critique, décalez les autres au besoin. Cela vous laisse +16.000 NOPS à dépenser comme il vous chante, avant l'interruption critique.
Vous avez mal à la tête? Utilisez déjà la pile pour effacer, écrire ou lire des données à usage unique, ça fait déjà pas mal d'usages très intéressants. Vous aurez tout le temps de tuner une lecture périodique avec la pratique.