gint : un noyau pour développer des add-ins
Posté le 20/02/2015 17:30
Ce topic fait partie de la série de topics du fxSDK.
En plus des options de programmation intégrée comme le Basic Casio ou Python, la plupart des calculatrices Casio supportent des
add-ins, des programmes natifs très polyvalents avec d'excellentes performances. Les add-ins sont généralement programmés en C/C++ avec l'aide d'un ensemble d'outils appelé SDK.
Plusieurs SDK ont été utilisés par la communauté avec le temps. D'abord le
fx-9860G SDK de Casio avec fxlib pour Graph monochromes (plus maintenu depuis longtemps). Puis le
PrizmSDK avec libfxcg pour Prizm et Graph 90+E (encore un peu actif sur Cemetech). Et plus récemment celui que je maintiens, le
fxSDK, dont gint est le composant principal.
gint est un unikernel, ce qui veut dire qu'il embarque essentiellement un OS indépendant dans les add-ins au lieu d'utiliser les fonctions de l'OS de Casio. Ça lui permet beaucoup de finesse sur le contrôle du matériel, notamment la mémoire, le clavier, l'écran et les horloges ; mais aussi de meilleures performances sur le dessin, les drivers et la gestion des interruptions, plus des choses entièrement nouvelles comme le moteur de gris sur Graph monochromes.
Les sources de gint sont sur la forge de Planète Casio :
dépôt Gitea Lephenixnoir/gint
Aperçu des fonctionnalités
Les fonctionnalités phares de gint (avec le fxSDK) incluent :
- Toutes vos images et polices converties automatiquement depuis le PNG, sans code à copier (via fxconv)
- Un contrôle détaillé du clavier, avec un GetKey() personnalisable et un système d'événements à la SDL
- Une bibliothèque standard C plus fournie que celle de Casio (voir fxlibc), et la majorité de la bibliothèque C++
- Plein de raccourcis pratiques, comme pour afficher la valeur d'une variable : dprint(1,1,"x=%d",x)
- Des fonctions de dessin, d'images et de texte optimisées à la main et super rapides, surtout sur Graph 90+E
- Des timers très précis (60 ns / 30 µs selon les cas, au lieu des 25 ms de l'OS), indispensables pour les jeux
- Captures d'écran et capture vidéo des add-ins par USB, en temps réel (via fxlink)
Avec quelques mentions spéciales sur les Graph monochromes :
Un moteur de gris pour faire des jeux en 4 couleurs !
La compatibilité SH3, SH4 et Graph 35+E II, avec un seul fichier g1a
Une API Unix/POSIX et standard C pour accéder au système de fichiers (Graph 35+E II seulement)
Et quelques mentions spéciales sur les Graph 90+E :
Une nouvelle police de texte, plus lisible et économe en espace
Le dessin en plein écran, sans les bordures blanches et la barre de statut !
Un driver écran capable de triple-buffering
Une API Unix/POSIX et standard C pour accéder au système de fichiers
Galerie d'add-ins et de photos
Voici quelques photos et add-ins réalisés avec gint au cours des années !
Arena (2016) — Plague (2021)
Rogue Life (2021)
Momento (2021)
Communication avec le PC (cliquez pour agrandir)
Utiliser gint pour développer des add-ins
Les instructions pour installer et utiliser gint sont données dans les divers tutoriels recensés dans le
topic du fxSDK. Il y a différentes méthodes de la plus automatique (GiteaPC) à la plus manuelle (compilation/installation de chaque dépôt). Le fxSDK est compatible avec Linux, Mac OS, et marche aussi sous Windows avec l'aide de WSL, donc normalement tout le monde est couvert
Notez en particulier qu'il y a des
tutoriels de développement qui couvrent les bases ; tout le reste est expliqué dans les en-têtes (fichiers
.h) de la bibliothèque que vous pouvez
consulter en ligne, ou dans les ajouts aux changelogs ci-dessous.
Changelog et informations techniques
Pour tester les fonctionnalités et la compatibilité de gint, j'utilise un add-in de test appelé gintctl (
dépôt Gitea Lephenixnoir/gintctl). Il contient aussi une poignée d'utilitaires d'ordre général.
Ci-dessous se trouve la liste des posts indiquant les nouvelles versions de gint, et des liens vers des instructions/tutoriels supplémentaires qui accompagnent ces versions.
Anecdotes et bugs pétés
Ô amateurs de bas niveau, j'espère que vous ne tomberez pas dans les mêmes pièges que moi.
TODO list pour les prochaines versions (2023-04-03)
gint 2.11
- Changements de contextes CPU. À reprendre du prototype de threading de Yatis pour permettre l'implémentation d'un véritable ordonnanceur. Demandé par si pour faire du threading Java.
- Applications USB. Ajouter le support de descripteurs de fichiers USB. Potentiellement pousser jusqu'à avoir GDB pour debugger.
- Support de scanf() dans la fxlibc. Codé par SlyVTT, plus qu'à nettoyer et fusionner.
Non classé
- Regarder du côté serial (plus facile que l'USB) pour la communication inter-calculatrices (multijoueur) et ultimement l'audio (libsnd de TSWilliamson).
- Un système pour recompiler des add-ins mono sur la Graph 90+E avec une adaptation automatique.
- Support des fichiers en RAM pour pouvoir utiliser l'API haut-niveau sur tous les modèles et éviter la lenteur de BFile à l'écriture quand on a assez de RAM.
Citer : Posté le 26/05/2021 14:36 | #
Tu donnes pas de contexte, c'est dur de t'aider. J'avais des erreurs similaires et supprimer build-{cg-fx} puis rebuild les a fait disparaitre.
Citer : Posté le 26/05/2021 14:36 | #
<setjmp.h> n'est implémenté que côté Vhex pour l'instant. Regarde dans le fichier STATUS, tu auras une bonne idée de ce qui est disponible (le DONE).
Citer : Posté le 26/05/2021 16:37 | #
Aussi, j'arrive pas a faire un malloc sans que le linker m'engeule pour des memset non definis
Citer : Posté le 26/05/2021 17:37 | #
Euh ça c'est pas normal par contre. Tu peux me montrer vite fait la commande de link (fxsdk build-{fx,cg} VERBOSE=1) et l'inclusion des libs dans le CMakeLists.txt ?
Citer : Posté le 26/05/2021 17:45 | #
Voici les logs de la compilation(le VERBOSE=1 ne change rien de special)
Et je build avec Make(pas cmake)
Citer : Posté le 26/05/2021 17:52 | #
Tu n'as probablement pas ajouté les bons flags. Il te faut :
-lgint-fx -lc -lopenlibm
Ou pareil en CG.
Citer : Posté le 26/05/2021 17:58 | #
A ba ca marche, merci!
Citer : Posté le 26/05/2021 21:28 | #
Nouvelle version : gint 2.5.0
Release associée du fxSDK : fxSDK 2.5.0
Voici une assez grosse release avec des nouveautés très concrètes pour vous et pas trop de changements !
Plusieurs de ces éléments sont gros, je vais ajouter ci-dessous un message pour expliquer la libc plus en détail et un autre pour l'utilisation du module USB.
Nouveautés importantes :
Modifications :
Corrections de bugs :
Citer : Posté le 26/05/2021 21:51 | #
Énorme cette mise à jour ! Je testerai les screenshots et l'USB dès que possible, est-ce que tu as fait des tests en rafale pour une capture vidéo du pauvre ?
Je sais que c'est mineur, mais je vois déjà des utilisations pour C_INVERT : ça va être très pratique pour des effets puissants à faible coût
J'avais regardé un peu votre libc, où est-ce que vont les flux standards ? (stdin, stdout, etc.)
Tu as oublié l'enum BFile dans tes ajouts
Ajouté le 27/05/2021 à 10:49 :
Bonjour, j'ai tenté les screenshots hier et aujourd'hui avec peu de succès.
J'ai fait des modifications minimales dans ce commit pour être sûr que ce commit pour être sûr que cela ne vienne pas de mon code :
=> https://gitea.planet-casio.com/KikooDX/pcadmin/compare/97ab19...de94db
Sur mon ordinateur, j'ai essayé de lancer fxlink -iw :
avant le transfert
après le transfert
en utilisateur régulier et en super-administrateur
Dans tous les cas, le log de fxlink reste vide et la calculatrice freeze quand elle arrive au screenshot.
fxlink -l fonctionne comme attendu.
Tout est à jour, j'ai même forcé l'update du fxsdk sur le tag 2.5.0.
Merci pour votre attention
Citer : Posté le 27/05/2021 10:57 | #
Aha, tu vas plus vite que le tutoriel. Tu n'as pas ouvert la connexion USB.
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
// Mieux vaut ouvrir la connexion une seule fois
usb_open(interfaces, GINT_CALL_NULL);
// Si tu veux screen dès que la connexion est ouverte, tu
// peux tester usb_is_open() à chaque tour de boucle, ou te
// faire notifier avec le callback de usb_open()
if(key == KEY_F6 && usb_is_open()) {
usb_fxlink_screenshot(true);
}
// À la fin du programme
usb_close();
Citer : Posté le 27/05/2021 11:14 | # | Fichier joint
Wow merci, j'ai appliqué ces changements et ça fonctionne du feu de dieu ! Mon premier screenshot en pièce jointe
Citer : Posté le 27/05/2021 11:16 | #
Let's gooo
Citer : Posté le 27/05/2021 11:34 | # | Fichier joint
J'ai essayé de faire une vidéo avec la rafale sur un projet que je ne nommerai point, ça rame un peu et ça crash de manière aléatoire mais c'est une vidéo et je suis trop content !
Citer : Posté le 27/05/2021 11:36 | #
GG ! Ça crashe par contre ?! Tu me fais un peu peur, je testerai plus en détail.
Citer : Posté le 27/05/2021 11:41 | #
En effet, pour être précis fxlink reçoit 1336 octets (au lieu de 2048) avant de s'arrêter correctement, tandis que la calculatrice freeze. Ça m'arrive quand je change de « scène » en jeu, ce qui ne devrait pas avoir d'impact étant donné que j'ai mis le code de screenshot dans ma fonction de raffraichissement.
Citer : Posté le 27/05/2021 11:48 | #
Aha ! Un freeze n'est pas un crash (System ERROR ou redémarrage), et je préfère d'autant que ça freeze. Pas idéal toujours, mais je suis rassuré.
Le screenshot est synchrone donc il ne devrait pas y avoir de mauvaises surprises sauf peut-être si tu le lances immédiatement après dupdate() (?). Pour faciliter la reproduction du bug, si tu as moyen de pousser le code sur ton dépôt, je prends.
Citer : Posté le 27/05/2021 11:50 | #
Je lance le screenshot immédiatement après dupdate, je vais pousser sur mon dépôt branche bordel dans deux minutes.
Et c'est ainsi que Projet Secret™ devint public.
Ajouté le 27/05/2021 à 11:52 :
https://git.sr.ht/~kikoodx/mtem/tree/bordel
J'espère vraiment que ça t'aidera :/
EDIT : je l'ai repassé en privé
Citer : Posté le 27/05/2021 13:21 | #
Merci ! Mais je n'ai pas eu l'occasion de voir, j'étais parti manger. (En MP si tu as encore la patience ? Sinon laisse tomber.)
Ajouté le 27/05/2021 à 16:17 :
Je réalise que je n'ai pas répondu aux autres remarques !
J'avais regardé un peu votre libc, où est-ce que vont les flux standards ? (stdin, stdout, etc.)
Nulle part pour l'instant, et rien n'est décidé. Je ne suis pas particulièrement chaud pour avoir un terminal intégré à gint, ce n'est pas la philosophie. Mais je pense proposer des fonctions supplémentaires (au moins dans la version gint) pour les rediriger vers du code utilisateur, auquel cas on peut faire un terminal dans une lib ou dans une application, sans problème.
Qui se sert des fonctions de recherche de BFile hmm?
Citer : Posté le 28/05/2021 14:52 | #
Voilà les informations pour l'USB pour cette release. C'est un début de tutoriel pour quelque chose qui sera sans doute développé dans les tutoriels officiels plus tard.
Edit : Maintenant que je l'ai fini je peux clairement dire que c'est plus qu'un début lol.
1. Interfaces et classes
2. Ouvrir et fermer la connexion USB
3. Connecter la calculatrice à fxlink
4. Endpoints USB et types de communication
5. Obtenir les numéros de pipes
6. Écrire sur un pipe : méthodes synchrone et asynchrone
7. Options d'écriture
8. Protocole fxlink et API simplifiée
9. Résumé : faire des screenshots en deux temps trois appels
Tout ce qu'on fait ici c'est de l'USB 2. La spécification USB est très compliquée, mais je vais me concentrer sur les parties utiles.
1. Interfaces et classes
Un périphérique USB 2 expose des interfaces, qui représentent les fonctionnalités de l'objet. Par exemple, un ensemble scanner/imprimante peut avoir deux interfaces, une pour la fonction scanner et une pour la fonction imprimante. Chaque interface a une classe, qui indique le rôle standard de l'objet. La classe est un code d'un octet dont la signification est fixée par le consortium USB. On trouve par exemple...
La classe donne à l'hôte (l'ordinateur) une indication de comment communiquer avec le périphérique, et c'est grâce à ça que l'ordinateur décide de faire apparaître une clé USB dans l'explorateur de fichiers ou une webcam dans le lecteur vidéo.
Le driver USB de gint permet d'exposer une ou plusieurs interfaces à la fois (pourvu que les interfaces soient codées évidemment). Pour l'instant, la seule interface disponible est nommée ff_bulk et utilise le code de classe ff qui est une sorte de catégorie « autres » pour laquelle l'ordinateur ne fait rien (ce qui veut dire qu'on a besoin d'un logiciel fait maison pour communiquer avec ; j'y reviendrai). Et comme l'USB c'est compliqué, pour l'instant on ne peut que transmettre de la calculatrice au PC (mais ça viendra).
2. Ouvrir et fermer la connexion USB
La connexion USB est ouverte par la fonction usb_open() à laquelle on donne une liste d'interfaces à ouvrir, et un callback (optionnel) qui est exécuté une fois que la connexion est établie. Dans sa forme la plus simple, ça donne quelque chose comme ça :
#include <gint/usb-ff-bulk.h>
// Liste des interfaces à ouvrir
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
usb_open(interfaces, GINT_CALL_NULL);
Faites bien attention au fait que usb_open() retourne même avant que la connexion ne soit ouverte ; le temps que ça prend dépend de ce que le PC fait donc c'est impossible de le prédire ; cela dit, en règle générale, ça prend 1 seconde environ.
Si vous voulez attendre que la connexion soit ouverte pour continuer le programme, vous pouvez utiliser le callback pour passer une variable à 1 et ensuite attendre que ça se produise.
volatile int usb_has_opened = 0;
usb_open(interfaces, GINT_CALL_SET(&usb_has_opened));
while(usb_has_opened == 0) sleep();
(La fonction sleep() de <gint/cpu.h> permet au processeur de dormir pendant l'attente, ce qui évite de consommer des piles.)
Il y a aussi une fonction usb_is_open() qui indique si le port est ouvert, vous pouvez attendre avec.
usb_open(interfaces, GINT_CALL_NULL);
while(!usb_is_open()) sleep();
Ouvrir la connexion USB notifie l'ordinateur que la calculatrice existe et initie les communications. Les interfaces offertes par gint ne peuvent plus changer jusqu'à ce que la connexion soit fermée. Généralement, je vous conseille de laisser la connexion ouverte si vous voulez communiquer fréquemment, plutôt que de la fermer et l'ouvrir toutes les quelques secondes (ce qui revient, du point de vue de l'ordinateur, à déconnecter et reconnecter le câble toutes les quelques secondes).
Quand vous avez fini, fermez la connexion avec usb_close() ; vous pouvez alors la rouvrir plus tard (y compris avec d'autres interfaces si ça vous chante).
3. Connecter la calculatrice à fxlink
fxlink est un outil du fxSDK qui permet de communiquer avec la calculatrice de deux façons :
Une fois le port ouvert, vous pouvez utiliser fxlink -l pour lister les calculatrices connectées. Dans mon cas, j'obtiens ma Graph 90+E qui donne ceci :
fx-CG or G-III series (USB Mass Storage) calculator
Device location: Bus 1, Port 2, Device 21
Identification: idVendor: 07cf, idProduct: 6102
Guessed sysfs path: /sys/bus/usb/devices/1-2/
Serial number: IGQcGRe9
Properties: mass_storage, serial_number=IGQcGRe9
Peu d'informations sont disponibles pour détecter le modèle exact de calculatrice, mais fxlink peut lister le numéro de série et distinguer les calculatrices qui utilisent FA-124 des modèles « clé USB ». Les informations sont résumées dans "Properties" (et si vous avez plusieurs calculatrices vous pouvez utiliser ces propriétés pour sélectionner celle avec laquelle vous voulez communiquer).
La communication avec l'interface usb_ff_bulk se fait avec le mode « interactif » de fxlink (-i), où fxlink se connecte à la calculatrice quand la connexion est ouverte, puis sauvegarde tout ce qui lui est envoyé jusqu'à ce qu'il soit arrêté ou que la calculatrice se déconnecte.
Si vous lancez fxlink -i, fxlink va chercher une calculatrice dont la connexion est déjà ouverte. Devoir lancer la calculatrice avant fxlink c'est casse-pieds, donc j'ai ajouté une option -w qui indique à fxlink d'attendre si aucune calculatrice est connectée. On peut donner un délai en secondes (-w5) ou bien pas de délai du tout, auquel cas l'attente est infinie (-w). De façon générale lancez le mode interactif avec fxlink -iw comme ça vous pouvez lancer fxlink et la calto dans n'importe quel ordre.
Connected to 1:21, starting test.
4. Endpoints USB et types de communication
Chaque interface USB définit plusieurs endpoints qui représentent les canaux de transmission entre l'hôte et le périphérique. Un endpoint a plusieurs propriétés, mais celles qui nous intéressent sont la direction et le type de transfert.
La direction c'est facile, c'est soit calto → PC soit PC → calto. Pour l'instant, gint ne sait communiquer que dans le sens calto → PC.
USB 2 définit quatre types de transfert :
Comme son nom l'indique, la class usb_ff_bulk fournit des transferts bulk. Actuellement elle a un unique endpoint, qui va dans le sens calto → PC.
(Vous noterez que dans un transfert bulk, c'est le PC qui initie les transferts, pas la calto. En principe je pense qu'il faudrait avec un endpoint interruption pour que la calto puisse notifier le PC quand elle veut faire un transfert. Mais pour l'instant fxlink se contente de demander des transferts en permanence ; c'est un hack qui sera corrigé dans le futur.)
5. Obtenir les numéros de pipes
Les parties suivantes décrivent l'API USB complète et détaillée. L'interface usb_ff_bulk propose une API très simplifiée pour communiquer directement avex fxlink. Vous pouvez sauter à cette partie si la suite vous paraît trop compliquée.
Dans gint, les interfaces déclarent les endpoints qu'elles veulent exposer, les paramètres pour les pipes, et la mémoire FIFO à réserver dans le module USB pour les transferts. Lorsque vous lancez usb_open(), le driver USB de gint consulte les besoins de toutes les interfaces, détermine si le module USB a assez de ressources (il n'y a que 16 endpoints et 16 kio de mémoire FIFO en tout), et si oui attribute des ressources à chaque interface avant de se connecter au PC.
Dans la calculatrice, chaque endpoint est associé à un pipe, qui est une partie du module USB avec laquelle on peut lire et écrire des données dans des endpoints. L'API gint utilise des numéros de pipes en paramètre un peu partout. La fonction usb_interface_pipe() permet de trouver le numéro de pipe associé à un endpoint d'une interface.
Par exemple, l'endpoint de communication bulk calto → PC de l'interface usb_ff_bulk a le numéro 0x81 (le bit 0x80 indique la direction), et donc on peut connaître le numéro de pipe associé comme ceci.
Évidemment ce n'est pas top d'avoir à connaître la valeur magique 0x81, donc l'interface peut fournir à la place une fonction pour obtenir le numéro du pipe directement. Par exemple usb_ff_bulk_output() :
Voilà comment on obtient les numéros de pipes pour communiquer sur un endpoint choisi.
6. Écrire sur un pipe : méthodes synchrone et asynchrone
On arrive ici dans le coeur du jeu. Les écritures sur le pipe se font par séquences : vous écrivez un bloc de données, puis un autre, un troisième, etc. jusqu'à avoir fini, et ensuite vous validez le transfert. Les données sont envoyées au PC dans deux situations :
La fonction la plus simple pour faire une écriture est usb_write_sync(). Elle écrite le bloc donné en paramètre dans le pipe ; si le pipe est plein elle transfère les données et attend que le transfert se termine, puis continue d'écrire jusqu'à ce que le bloc soit fini. Une fois le bloc fini elle n'envoie pas les contenus du pipe, parce qu'on part du principe que vous pouvez avez d'autres choses à écrire après (dans un autre bloc de mémoire), prolongeant la séquence.
Pour valider une série d'écritures, utilisez usb_commit_sync(), qui va envoyer tout ce qui a été écrit précédemment mais pas encore envoyé, et retournera quand ce sera fini.
int pipe = usb_ff_bulk_output();
usb_write_sync(pipe, "Yeah!", 5, 1, false);
usb_write_sync(pipe, "Hello, World!", 14, 1, false);
usb_commit_sync(pipe);
Ces deux fonctions sont dites synchrones parce qu'elles synchronisent votre programme avec le transfert USB : le programme ne reprend qu'une fois le transfert fini. Et c'est parfois dommage parce qu'il y a plein de choses que votre programme pourrait faire au lieu de regarder le module USB communiquer avec l'ordinateur (ce qui n'occupe pas le processeur !).
gint implémente donc aussi l'option supérieure (mais plus compliquée) des transferts asynchrones. Pour faire une écriture aysnchrone, utilisez usb_write_async(). Le fonctionnement est le même que usb_write_sync(), mais le timing est différent. S'il y a beaucoup de données à écrire, usb_write_async() remplit le pipe et note dans une variable quelle partie du bloc n'a pas pu être écrite. Ensuite, elle retourne immédiatement, ce qui permet à votre programme de reprendre pendant que le transfert se fait. Les écritures et les transferts vont alors s'enchaîner en tâche de fond (par interruptions) pendant que votre programme est occupé à faire autre chose. En somme, usb_write_async() retourne presque immédiatement et fait tout en tâche de fond.
Pour vous permettre de savoir que l'écriture se finit, usb_write_async() a comme argument supplémentaire un callback qui est appelé une fois que le bloc est terminé. Il en va de même pour usb_commit_async(), qui lance le transfert de ce qui a été écrit dans le pipe mais pas envoyé, et retourne immédiatement pendant que le transfert se fait en tâche de fond. L'argument final de usb_commit_async() est un callback qui est invoqué une fois le transfert fini.
On peut se servir du callback pour continuer les opérations ; par exemple, on peut refaire le même transfert que tout à l'heure en asynchrone comme ceci :
{
usb_write_async(pipe, "Yeah!", 5, 1, false, GINT_CALL(step_2, pipe));
}
void step_2(int pipe)
{
usb_write_async(pipe, "Hello, World!", 14, 1, false, GINT_CALL(step_3, pipe));
}
void step_3(int pipe)
{
usb_commit_async(pipe, GINT_CALL_NULL);
}
step_1();
C'est plus verbeux mais aussi beaucoup, beaucoup plus puissant, car le programme peut continuer de faire des tâches pendant le transfert, qui ne coûte quasiment pas de CPU ; il coûte surtout de la bande passante sur la mémoire.
Pour des petits transferts comme ceux-ci (5 et 14 octets) surtout ne vous emmerdez pas, faites tout en synchrone. L'asynchrone est important pour les gros transferts du fait du temps économisé sur le CPU. Vous pouvez aussi mélanger les deux à volonté pour avoir moins de callacks à faire, notez juste que usb_write_async() et usb_commit_async() renvoient un code d'erreur si vous les appelez pendant qu'une écriture ou un commit est en cours (les versions synchrones attendent que l'opération en cours se termine et ne vous poseront jamais de problème).
Notez que les callbacks sont exécutés dans un gestionnaire d'interruption donc il y a des limitations ; en gros vous pouvez voir ça comme un autre thread. Les subtilités classiques de la programmation parallèle et asynchrone s'appliquent.
7. Options d'écriture
Si vous avez regardé les paramètres de usb_write_sync(), vous aurez remarqué deux paramètres unit_size et use_dma.
Le premier, unit_size, représente la taille des accès au pipe (1, 2 ou 4 octets). Si l'alignement du bloc et sa taille sont multiples de 4, vous pouvez mettre 4, sinon s'ils sont multiples de 2 vous pouvez mettre 2, sinon mettez 1. Pour les gros blocs c'est avantageux de mettre 4, parce que ça va plus vite. Si vous avez un doute mettez 1, ça marchera à tous les coups. Notez que vous devez utiliser la même taille unitaire pour toutes les écritures d'une séquence.
Le second, use_dma, indique s'il faut utiliser le DMA pour écrire dans le pipe (plutôt que le CPU). En termes de vitesse ça ne change rien car c'est le pipe le plus lent dans l'histoire (et le CPU et le DMA arrivent tous les deux à écrire assez vite pour le saturer). Mais si le DMA fait l'écriture le programme peut continuer de s'exécuter sur le CPU (ce qui vous permet dans le cas ultime d'avoir le CPU qui calcule, le DMA qui écrit dans le pipe, et le module USB qui transfère en même temps ! x3). Si vous faites de l'asynchrone et que vous avez du gros calcul à faire, n'hésitez pas à l'utiliser, c'est gratuit. Mais si c'est pour faire du dessin dans la VRAM vous allez juste gêner l'écriture, dans ce cas laissez l'écriture finir et dessinez ensuite.
8. Protocole fxlink et API simplifiée
fxlink écoute tout ce qui se dit sur l'endpoint bulk de la classe usb_ff_bulk, et supporte un petit protocole par lequel vous pouvez envoyer des messages. Pour ceux qui ont lu la version détaillée : c'est tout du synchrone.
Par exemple, la fonction usb_fxlink_text() enverra un message à fxlink contenant un en-tête (indiquant à fxlink que c'est du texte) et le texte à proprement parler (ce qui fait donc deux écritures et un commit). fxlink, voyant le texte arriver, l'affiche sur le terminal.
Connected to 1:21, starting test.
New message (v1.0): application 'fxlink', type 'text', size 20 bytes
Successfully read 20 bytes
------------------
Hi! This is gintctl!
------------------
La ligne "New message" résume les informations inscrites dans l'en-tête. Le type "text" détermine ce que fxlink en fait.
La fonction usb_fxlink_screenshot() envoie un autre type de message ("image") avec une copie soit de la VRAM soit de ce qui est affiché à l'écran.
Successfully read 177420 bytes
Saved image (396x224, format=0) to './fxlink-image-2021.04.28-14h26-1.png'
Comme vous pouvez le voir, fxlink sauvegarde les images dans des fichiers (dans le dossier courant), avec un nom choisi en fonction de la date et de l'heure, et convertit tout en un joli PNG. C'est la méthode la plus simple pour faire un screenshot !
Pour les Graph mono utilisant le moteur de gris, usb_fxlink_screenshot_gray() fait pareil pour les screenshots en gris. La raison pour laquelle c'est séparé c'est que si vous l'utilisez le moteur de gris sera forcément dans l'add-in, et je voudrais pas que simplement prendre des screenshots d'un add-in noir et blanc vous force à avoir le moteur de gris (ce qui prendrait inutilement de la place).
Vous pouvez envoyer des messages personnalisés à fxlink, mais pas sauvagement comme je l'ai fait tout à l'heure avec mes "Yeah!" et autres "Hello, World!". Il faut inclure un en-tête sinon fxlink sera confus. Vous pouvez remplir un en-tête avec usb_fxlink_fill_header(), où vous devez spécifier le nom d'une application, le type de message, et la taille des données que vous allez envoyer (généralement en une séquence finie par un commit).
Le nom d'application "fxlink" est réservé et représente tous les types pour lesquels fxlink effectue un traitement particulier. Pour l'instant, tous les autres messages sont enregistrés dans des fichiers binaires, mais l'idée (dans le futur) c'est que si vous mettez un nom d'application X fxlink appelera un exécutable fxlink-message-X sur le PC (que vous fournissez) et comme ça vous pouvez récupérer les données dans un programme de votre choix (et les traiter, afficher, convertir, ouvrir dans un logiciel, etc).
Pour un exemple simple à copier, regardez usb_fxlink_text(), pour un exemple un peu plus compliqué regardez usb_fxlink_screenshot() qui a aussi un sous-en-tête donnant les dimensions et le format de l'image.
9. Résumé : faire des screenshots en deux temps trois appels
Au début de l'add-in, ouvrez la communication USB.
#include <gint/usb-ff-bulk.h>
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
usb_open(interfaces, GINT_CALL_NULL);
Au moment où vous voulez prendre un screenshot, utilisez usb_fxlink_screenshot(). La connexion doit être ouverte donc je vous conseille d'attendre une pression de touche (par exemple VARS).
usb_fxlink_screenshot(true);
}
Sur le PC, lancez fxlink -iw et récupérez les images.
Connected to 1:21, starting test.
New message (v1.0): application 'fxlink', type 'image', size 177420 bytes
Successfully read 177420 bytes
Saved image (396x224, format=0) to './fxlink-image-2021.04.28-14h26-1.png'
À la fin de l'add-in, fermez la connexion USB (fxlink se fermera tout seul ; vous pouvez aussi le couper avec Ctrl-C).
Si vous tentez de prendre un screenshot alors que fxlink n'est pas ouvert, le programme peut freeze. Dans ce cas lancez fxlink pour continuer l'exécution.
Citer : Posté le 28/05/2021 19:00 | #
Hey !
Trop bien tout ça !
J'ai mis à jour avec GiteaPC pour tester ca, cependant j'obtiens une erreur lors de la compilation : fatal error: gint/usb.h: No such file or directory
Voici mon code :
#include <gint/keyboard.h>
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
int main(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
dupdate();
getkey();
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
usb_open(interfaces, GINT_CALL_NULL);
if(key == KEY_VARS && usb_is_open()) {
usb_fxlink_screenshot(true);
}
usb_close();
}
EDIT : autre chose, comment installer FXLink ?
Citer : Posté le 28/05/2021 20:40 | #
<gint/usb.h> est présent depuis gint 2.4.0 (ie. la release d'avant), et fxlink fait partie du fxSDK aussi depuis cette release, donc ta mise à jour n'a pas dû marcher.
Si tu n'es pas sur une branche, tu peux toujours faire
Au passage le code que j'ai donné dans le post ci-dessus n'est pas un copier/coller, il faut le lire et le comprendre - tu n'as même pas déclaré la variable key, ça ne risque pas de marcher.