La Place des Développeurs Coder en C avec SDCC
ericb59
Membre non connecté
Conseiller Municipal
Reprise du message précédent
Ben, le gars demande comment faire une PAUSE, pas de faire une synchro...Ma routine mets en pause le programme pour X frames...
Je ne comprend pas où tu veux en venir en fait...
aoineko
Membre non connecté
Conseiller Municipal
Je peux me tromper, mais je pense pas qu'il souhaite mettre son MSX en pause et ne rien en faire pendant X frames. A mon avis, il veux juste un moyen de synchroniser sa boucle de gameplay pour qu'elle ait une durée fixe et consistante... sans pour autant s'empêcher d'utiliser ce temps pour faire des choses.
On est toujours ignorant avant de savoir.
Citation :
How do I make a pause? I.e. in milliseconds or something, just so I can adjust the speed of the program.
Non, le gars veut juste une routine de pause standard, du genre wait(3) pour attendre 3 millisecondes.
Un bête FORI=0TO1000:NEXTI, quoi ...
MSX1: Daewoo DPC-200 / Yamaha CX5M
MSX2: Sony HB-F9P
MSXVR
Vidéo: V9990 (GFX-9)
Audio: MSX-Music (FM-PAC) / MSX-Audio (Audiowave) / OPL4 (Monster Sound FM Blaster) / OPNB (Neotron)
ericb59
Membre non connecté
Conseiller Municipal
aoineko
Membre non connecté
Conseiller Municipal
Tu as vraiment de la chance, je l'avais faite y a longtemps, mais je l'ai justement corrigé hier !
En fait, le trick, comme pour la plupart des zones dédiés au VDP en VRAM, c'est qu'en fonction du mode écran, il y a des particularités à tenir compte.
Voici mes fonctions :
- VADDR correspond soit à un 16-bits pour MSX1 ou si on souhaite n'utiliser que 64K de VRAM, soit à un 32-bits pour accéder à 128K de VRAM.
- g_VDP_Data.Mode est une variable global qui stock le screen mode (tu peux utiliser l'info en RAM, SCRMOD (en 0xFCAF), si tu gardes le Bios).
- Je stock l'adresse des tables dans des variables (pas juste la valeur du registre correspondant) pour accélérer l'écriture en VRAM ultérieur. Toutes les fonctions de manipulation des sprites, utilisent ces valeurs.
Hier j'ai commencé un tableau pour lister les particularités des tables du VDP :
Comme c'était pour mon projet MSX1, je n'ai fini que la partie des screen modes MSX1 mais tu peux déjà voir tous les cas particuliers qu'il faut gérer niveau code.
Ma lib est à jours de tous ces cas ; je te préviendrais quand je commit-erai sur GitHub (surement demain soir) comme ça tu auras toutes mes fonctions comme réf.
EDIT : J'ai mis à jour l'image avec tous les screen modes. J'au aussi upload un PDF avec toutes les infos : ScreenModeTables.pdf
En fait, le trick, comme pour la plupart des zones dédiés au VDP en VRAM, c'est qu'en fonction du mode écran, il y a des particularités à tenir compte.
Voici mes fonctions :
Code C :
//----------------------------------------------------------------------------- /// Set sprite attribute table address /// @param addr VRAM address where to place the table (16 or 17-bits long depending on VDP_VRAM_ADDR definition) /// Address must be a multiple of 80h for MSX1 screen modes and multiple of 200h for MSX2 ones. void VDP_SetSpriteAttributeTable(VADDR addr) __FASTCALL { g_SpriteAtributeLow = (u16)addr; u8 reg; reg = (u8)(addr >> 7); #if (MSX_VERSION >= MSX_2) switch(g_VDP_Data.Mode) { case VDP_MODE_GRAPHIC4: case VDP_MODE_GRAPHIC5: case VDP_MODE_GRAPHIC6: case VDP_MODE_GRAPHIC7: reg |= 0b111; break; }; #endif VDP_RegWrite(5, reg); #if (VDP_VRAM_ADDR == VDP_VRAM_ADDR_17) reg = (u8)(addr >> 15); VDP_RegWrite(11, reg); g_SpriteAtributeHigh = addr >> 16; #endif addr -= 0x200; g_SpriteColorLow = (u16)addr; #if (VDP_VRAM_ADDR == VDP_VRAM_ADDR_17) g_SpriteColorHigh = addr >> 16; #endif } //----------------------------------------------------------------------------- /// Set sprite pattern table address /// @param addr VRAM address where to place the table (16 or 17-bits long depending on VDP_VRAM_ADDR definition) /// Address must be a multiple of 800h. void VDP_SetSpritePatternTable(VADDR addr) __FASTCALL { g_SpritePatternLow = (u16)addr; #if (VDP_VRAM_ADDR == VDP_VRAM_ADDR_17) g_SpritePatternHigh = addr >> 16; #endif u8 reg = (u8)(addr >> 11); VDP_RegWrite(6, reg); }
- VADDR correspond soit à un 16-bits pour MSX1 ou si on souhaite n'utiliser que 64K de VRAM, soit à un 32-bits pour accéder à 128K de VRAM.
- g_VDP_Data.Mode est une variable global qui stock le screen mode (tu peux utiliser l'info en RAM, SCRMOD (en 0xFCAF), si tu gardes le Bios).
- Je stock l'adresse des tables dans des variables (pas juste la valeur du registre correspondant) pour accélérer l'écriture en VRAM ultérieur. Toutes les fonctions de manipulation des sprites, utilisent ces valeurs.
Hier j'ai commencé un tableau pour lister les particularités des tables du VDP :
Comme c'était pour mon projet MSX1, je n'ai fini que la partie des screen modes MSX1 mais tu peux déjà voir tous les cas particuliers qu'il faut gérer niveau code.
Ma lib est à jours de tous ces cas ; je te préviendrais quand je commit-erai sur GitHub (surement demain soir) comme ça tu auras toutes mes fonctions comme réf.
EDIT : J'ai mis à jour l'image avec tous les screen modes. J'au aussi upload un PDF avec toutes les infos : ScreenModeTables.pdf
On est toujours ignorant avant de savoir.
aoineko
Membre non connecté
Conseiller Municipal
Finalement, j'ai déjà tout commit sur GitHub.
Tu trouveras toutes les fonctions dans le module VDP : https://github.com/aoineko-fr/CMSX/blob/master/cmsx/src/vdp.c
Les fonctions en question :
Tu trouveras toutes les fonctions dans le module VDP : https://github.com/aoineko-fr/CMSX/blob/master/cmsx/src/vdp.c
Les fonctions en question :
Code C :
void VDP_SetLayoutTable(VADDR addr); // aussi connu sous le nom de Pattern Name Table (ou PNT) void VDP_SetColorTable(VADDR addr); void VDP_SetPatternTable(VADDR addr); // aussi connu sous le nom de Pattern Generator Table (ou PGT) void VDP_SetSpriteAttributeTable(VADDR addr); void VDP_SetSpritePatternTable(VADDR addr);
On est toujours ignorant avant de savoir.
aoineko
Membre non connecté
Conseiller Municipal
@Eric, j'ai fait un crt0 pour une ROM 48K (pages 0~2) qui vient remplacer le système d'interruption par défaut : https://github.com/aoineko-fr/CMSX/blob/master/cmsx/src/crt0/crt0_rom48_isr.asm
L'entête de la ROM est en 4000h (page 1) et le code de boot s'occupe de switch les pages 0 et 2 pour qu'elles soient sur le slot de la ROM, et d'installer le code d'interruption au bon endroit (0038h).
Le gros avantage, c'est qu'ensuite tu n'as plus besoin de faire de switch et tu as la maitrise totale du contenu des adresses 0000h à BFFFh.
La contrepartie, c'est que tu ne peux évidemment plus utiliser les routines du Bios.
C'est pas gênant pour moi vu que toute ma lib est faite pour ne pas dépendre du Bios mais dans Fusion-C, il faudrait bien faire attention aux fonctions qu'on utilise.
En tout cas, ça pourrait surement intéresser certains de tes utilisateurs.
L'entête de la ROM est en 4000h (page 1) et le code de boot s'occupe de switch les pages 0 et 2 pour qu'elles soient sur le slot de la ROM, et d'installer le code d'interruption au bon endroit (0038h).
Le gros avantage, c'est qu'ensuite tu n'as plus besoin de faire de switch et tu as la maitrise totale du contenu des adresses 0000h à BFFFh.
La contrepartie, c'est que tu ne peux évidemment plus utiliser les routines du Bios.
C'est pas gênant pour moi vu que toute ma lib est faite pour ne pas dépendre du Bios mais dans Fusion-C, il faudrait bien faire attention aux fonctions qu'on utilise.
En tout cas, ça pourrait surement intéresser certains de tes utilisateurs.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
aoineko
Membre non connecté
Conseiller Municipal
ericb59 :
Ca veut dire que ca serait bien que je vire tout mes appels au Bios ça !
Encore du boulot en perspective
Encore du boulot en perspective
Je trouve ça bien qu'on ait une lib C qui utilise le Bios.
Après, l'un empêche pas l'autre.
Ce que tu pourrais faire (plus tard), c'est juste marquer dans ta doc les fonctions compatibles ou non avec un ROM sans Bios.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
aoineko :
[quote=ericb59]
Après, l'un empêche pas l'autre.
Ce que tu pourrais faire (plus tard), c'est juste marquer dans ta doc les fonctions compatibles ou non avec un ROM sans Bios.
Après, l'un empêche pas l'autre.
Ce que tu pourrais faire (plus tard), c'est juste marquer dans ta doc les fonctions compatibles ou non avec un ROM sans Bios.
Oui c'e see y ce que j'avais prévu de faire.
L'une des fonctions à faire sans Bios qui m'ennuie le plus c'est la fonction "Screen"
pour initialiser un mode écran.
J'ai vu que de ton côté tu utilises des fonctions distinctes suivant le mode écran à initialiser.
c'edt le plus simple , mais ça s'éloigne de ma philosophie.
aoineko
Membre non connecté
Conseiller Municipal
Coté utilisateur, il n'y a qu'une fonction (https://github.com/aoineko-fr/CMSX/blob/master/cmsx/src/vdp.h) :
Les différentes fonctions du module VDP sont "invisibles".
D'ailleurs, je pourrais n'avoir qu'une seule fonction et utiliser le numéro du screen comme index dans un tableau avec les valeurs d'initialisation des registres.
Code C :
void VDP_SetMode(const u8 mode) __FASTCALL;
Les différentes fonctions du module VDP sont "invisibles".
D'ailleurs, je pourrais n'avoir qu'une seule fonction et utiliser le numéro du screen comme index dans un tableau avec les valeurs d'initialisation des registres.
On est toujours ignorant avant de savoir.
aoineko
Membre non connecté
Conseiller Municipal
Hello,
Voici une (demi) bonne nouvelle pour les programmeurs C !
Les auteurs de SDCC ont changé leur système de passage de paramètres aux fonctions, ce qui peut offrir des gains de performance importants.
A partir de la version 4.1.12, les paramètres sont maintenant transmit par défaut aux fonctions via les registres (sans passer par la pile) dans certains cas :
- 1 paramètre 8/16/32 bits (registres A/HL/HLDE ; l'équivalent de __z88dk_fastcall )
- 2 paramètres 8 bit (registres A et L)
- 1 paramètre 8/16 bits + 1 paramètre 16 bit (registres A/HL et DE)
Si la fonction possède plus de paramètres, les autres sont passés par la pile.
Voici le listing de tous les prototypes de fonction qui passe maintenant entièrement par les registres (avec le nom du registre associé) :
Si vous êtes attentifs, il n'y a qu'une combinaison de 2 paramètres qui n'est pas géré entièrement via les registres, c'est quand le 1er est un 16 bits et le 2e un 8 bits.
Dans ce cas, le 2e paramètres est passé par la pile.
Si au début, je parlais de "demi" bonne nouvelle, c'est que s'il s'agit d'une grosse amélioration pour un utilisateur standard de SDCC, pour ceux qui utilisaient déjà __z88dk_fastcall , ça n'apporte pas beaucoup de chose en plus.
La véritable révolution, ça serait de pouvoir choisir dans quel registre passer chaque paramètre (pour optimiser le code et interfacer facilement les librairies en assembleur et les BIOS).
A priori c'est envisagé, mais pas à court terme.
En tout cas, merci à la team SDCC pour son super compilateur.
Voici une (demi) bonne nouvelle pour les programmeurs C !
Les auteurs de SDCC ont changé leur système de passage de paramètres aux fonctions, ce qui peut offrir des gains de performance importants.
A partir de la version 4.1.12, les paramètres sont maintenant transmit par défaut aux fonctions via les registres (sans passer par la pile) dans certains cas :
- 1 paramètre 8/16/32 bits (registres A/HL/HLDE ; l'équivalent de __z88dk_fastcall )
- 2 paramètres 8 bit (registres A et L)
- 1 paramètre 8/16 bits + 1 paramètre 16 bit (registres A/HL et DE)
Si la fonction possède plus de paramètres, les autres sont passés par la pile.
Voici le listing de tous les prototypes de fonction qui passe maintenant entièrement par les registres (avec le nom du registre associé) :
Code C :
void f(char a); void f(short hl); void f(long dehl); void f(char a, char l); void f(char a, short de); void f(short hl, short de);
Si vous êtes attentifs, il n'y a qu'une combinaison de 2 paramètres qui n'est pas géré entièrement via les registres, c'est quand le 1er est un 16 bits et le 2e un 8 bits.
Dans ce cas, le 2e paramètres est passé par la pile.
Si au début, je parlais de "demi" bonne nouvelle, c'est que s'il s'agit d'une grosse amélioration pour un utilisateur standard de SDCC, pour ceux qui utilisaient déjà __z88dk_fastcall , ça n'apporte pas beaucoup de chose en plus.
La véritable révolution, ça serait de pouvoir choisir dans quel registre passer chaque paramètre (pour optimiser le code et interfacer facilement les librairies en assembleur et les BIOS).
A priori c'est envisagé, mais pas à court terme.
En tout cas, merci à la team SDCC pour son super compilateur.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
Intéressante nouveauté.
Merci pour ce point Aoineko.
Je me pose quand même des questions...
Est-ce que c'est totalement transparent ?
Y a t-il des précautions à prendre ? Dans le cas d'usage de routines ASM faut il sauver tous les registres dans la pile avant d'utiliser une routine ASM , et les de-poper avant la sortie ?
Est-ce que les anciennes sources fonctionnent sans modification? Edité par ericb59 Le 24/11/2021 à 07h20
Merci pour ce point Aoineko.
Je me pose quand même des questions...
Est-ce que c'est totalement transparent ?
Y a t-il des précautions à prendre ? Dans le cas d'usage de routines ASM faut il sauver tous les registres dans la pile avant d'utiliser une routine ASM , et les de-poper avant la sortie ?
Est-ce que les anciennes sources fonctionnent sans modification? Edité par ericb59 Le 24/11/2021 à 07h20
aoineko
Membre non connecté
Conseiller Municipal
Pour ceux qui ne codent qu'en C, ces modifications sont totalement transparentes.
Pour nous autre bidouilleur qui avons des tas de fonctions dont le corps est en assembleur en ligne, il faut faire attention effectivement.
Déjà, tu as un flag de compilation –-sdcccall 0 pour revenir à l'ancienne convention d'appel des fonctions et tu peux appliquer la nouvelle à la main, fonction par fonction, avec la directive __sdcccall(1) . Comme ça, pas de soucis.
Ou bien tu peux faire l'inverse et laisser globalement la nouvelle convention d'appel et la désactiver pour les fonctions ou tu sais que ça ne va pas marcher avec un __sdcccall(0) .
La 2e est bien meilleure en matière de performance, mais il faut faire attention à ne pas oublier de fonctions.
Personnellement, ce que je compte faire pour ma lib :
Entre chaque étape, il est important non seulement de compiler, mais surtout de bien tester le résultat car les bugs en assembleur ne se détectent généralement pas à la compilation.
Personnellement, j'ai une batterie de samples pour chaque partie de ma lib (VDP, PSG, input, ...) et je les testerai tous entre chaque étape pour être sûr.
Au final, le gain de performance devrait être assez significatif.
Pour nous autre bidouilleur qui avons des tas de fonctions dont le corps est en assembleur en ligne, il faut faire attention effectivement.
Déjà, tu as un flag de compilation –-sdcccall 0 pour revenir à l'ancienne convention d'appel des fonctions et tu peux appliquer la nouvelle à la main, fonction par fonction, avec la directive __sdcccall(1) . Comme ça, pas de soucis.
Ou bien tu peux faire l'inverse et laisser globalement la nouvelle convention d'appel et la désactiver pour les fonctions ou tu sais que ça ne va pas marcher avec un __sdcccall(0) .
La 2e est bien meilleure en matière de performance, mais il faut faire attention à ne pas oublier de fonctions.
Personnellement, ce que je compte faire pour ma lib :
- Compiler une première fois avec –-sdcccall 0 juste pour vérifier qu'il n'y a pas de soucis autre que le changement de convention d'appel.
- Remettre la nouvelle convention d'appel et la désactiver avec __sdcccall(0) sur toutes les fonctions avec de l'assembleur.
- Retirer __sdcccall(0) sur les fonctions qui ont déjà __z88dk_fastcall vu que c'est le même comportement (et du coup, retirer aussi __z88dk_fastcall ).
- Enfin, le plus long, réécrire les fonctions en assembleur, une par une, pour utiliser les registres plutôt que la pile et retirer le
__sdcccall(0)
quand c'est fait.
Entre chaque étape, il est important non seulement de compiler, mais surtout de bien tester le résultat car les bugs en assembleur ne se détectent généralement pas à la compilation.
Personnellement, j'ai une batterie de samples pour chaque partie de ma lib (VDP, PSG, input, ...) et je les testerai tous entre chaque étape pour être sûr.
Au final, le gain de performance devrait être assez significatif.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
Oui on va sans doute gagner de la vitesse mais c'est beaucoup de contraintes en attendant !
Toutes nos routines spécifiques sont en ASM, du coup y a quand même pas mal de boulot pour les remettre en conformité avec ce protocole.
J'suis fatigué rien qu'à y penser !
Toutes nos routines spécifiques sont en ASM, du coup y a quand même pas mal de boulot pour les remettre en conformité avec ce protocole.
J'suis fatigué rien qu'à y penser !
Répondre
Vous n'êtes pas autorisé à écrire dans cette catégorie