Portage de Doom sur carte d'évaluation STM32
Posté : 08 janv. 2020, 13:46
Doom !
DOOM !!
DOOOOOOOOOOM !!!
Je suis fasciné par ce jeu, son gameplay, son histoire, sa technique ... Et il se passe encore plein de choses autour de ce vénérable, culte et révolutionnaire titre sorti en 1993, dont le code source du moteur a été publié en open-source en 1997, et dont la communauté est encore très active. La publication en open-source a ouvert la voie à une multitude d'évolutions et d'expérimentations, allant de la simple adaptation ou amélioration au portage sur à peu près n'importe quoi : calculatrice programmable (les vraies grosses un peu modernes, je ne parle pas des knock-off sur Ti-83+), consoles de salon, distributeur de billets (no joke), tableau de bord de voiture (no joke !!), écran d'imprimante jet d'encre (no joke !!!), oscilloscope numérique (:|), thermostat (wtf ?) barre OLED de MacBook Pro (non mais wtf Doom en 640 x 12 ???) ...
If it exists and has a microprocessor and a screen, it can run Doom !
Mon moi du passé lointain (2 mois, une éternité ...) avait laissé à l'intention de mon moi du passé récent (2 semaines) un onglet ouvert sur le navigateur : https://github.com/floppes/stm32doom
Doom (plus exactement Chocolate Doom) pour carte d'évaluation STM32F429. Carte dont je dispose (qui n'est pas chère d'ailleurs, on la touche à ~$30) ... Ho ho ho ...
J'ai testé : ça marche, c'est ... dingue ! Et tellement cool
Par contre il y a quelques limitations (le portage a été fait par un mec tout seul en 2015), que je suis bien motivé pour essayer de faire sauter ou contourner:
- Pas de son.
- Pas de musique.
- Contrôles de merde. Le tactile, sur du FPS, comment dire ... Il me semble impératif de faire honneur à ce jeu en permettant d'y jouer avec les contrôleurs recommandés par la PC Gaming Master Race : le clavier et la souris !
- doom1.wad only. L'intégralté du WAD est chargée dans la RAM externe de la carte dévaluation, qui fait 8Mo. De fait, cela limite la taille du WAD à 8Mo, et dans les WAD "courants", il n'y a que celui de l'épisode 1 shareware (doom1.wad donc) qui rentre (il fait ~4Mo).
- Pas de sélection du WAD au démarrage, le nom du WAD à charger est écrit en dur dans le code.
Pour le son, il faut dire qu'il n'y a pas de sortie audio sur cette carte, mais ça peut s'arranger, il suffit de faire du PWM ou du PDM sur une sortie, voire du DAC (les DAC sur les STM32 tournent à 50kHz, pour pouvoir générer du 48kHz). Par contre, il faut faire un driver pour ça. Idem pour la musique, avec une contrainte supplémentaire : la musique est en MIDI, cela va de soi. Ce qui sous-entend qu'il faut interpréter les fichiers MIDI, et surtout synthétiser les sons des instruments Là on est pas dans le simple ...
Pour le contrôleur il y a deux approches : ajouter le support clavier souris, ou faire un contrôleur DIY spécifique. Pour le support du clavier / souris, il n'y a qu'un seul port USB (micro) en host sur la carte (et de toutes façons il n'y a qu'un seul USB possible en host sur cette gamme de STM32, que je sache). Donc il faut de toutes façons multiplexer, donc utiliser un HUB. Or, le support des HUB n'est pas natif dans les libs / stack USB "standard". En tous cas pas dans les couches ST, utilisées ici. Certaines stack implémentent le support des HUB, mais la grande majorité sont des libs non-free et payantes. La seule free que j'ai vu c'est emCraft. Je n'ai aucune idée de ce que c'est et de ce que ça vaut. Par contre, après avoir cherché un peu sur les forums, il semblerait qu'ajouter le support des HUBs USB soit loin d'être complexe, et que ça prendrait ~400 lignes de code, ce qui est non-négligeable, mais tout à fait raisonnable.
Pour un contrôleur DIY, je m'inspirerait bien du contrôleur que Roul et 3ds ont fait pour la brodeuse
Par contre pour la souris ... je pense qu'il faut une vraie souris.
Pour l'interface, soit on fait un contrôleur USB qu'on multiplexe (cf point précédent) soit on le connecte à des GPIO ou un bus de com du MCU.
Pour la RAM ... Vaste sujet. plusieurs options s'offrent à nous.
- Porter le projet sur une carte qui a plus de RAM (en Discovery yen a pas, même sur les cartes d'évaluation "chères")
- Ajouter une RAM supplémentaire (SPI ?)
- Changer la gestion du chargement des datas
Ce dernier point me semble plus intéressant. Il faut noter que 8Mo de RAM, en 1993 ('back in the days' comme on dit), c'était assez standard dans les PCs. Par contre, Vanilla Doom (et donc Chocolate Doom) sont des sourceports plus récent, dont l'objectif est de reproduire l'expérience de jeu la plus proche de l'original, tout en rendant l'utilisation simple sur les OS récents. Donc, comme beaucoup de sourceports, ils tirent parti des ressources disponibles sur les PCs récents, et entre autre la RAM illimitée (en comparaison de 93). Donc, à mon avis, le fait qu'il charge l'intégralité du WAD dans la RAm est un choix de design du sourceport utilisé, choix fait par commodité pour les PCs desktop, mais contraignant ici, vu que l'on est sur une plateforme donc la quantité de mémoire disponible est similaire aux PCs de 93. Par contre niveau puissance de calcul, on est à 180MHz, looooiiin devant les 486SX33 de l'époque et je ne parle pas des temps d'accès, vitesse des bus de com' ... Donc je pense qu'il serait pertinent de charger dynamiquement les datas dans la RAM, pour limiter le taux d'occupation à un instant t. Et je suis persuadé que c'est ce qu'ils font dans le moteur d'origine, donc il va falloir que je checke ça. Par exemple, garder en mémoire l'intégralité des niveaux n'a pas de sens, vu qu'on ne joue que dans un seul niveau à la fois. Donc quand on finit un niveau il faudrait vider les données du niveau qu'on vient de finir, et mettre la place les données du niveau suivant avant de le lancer.
(A noter que le portage sur d'autres cartes peut avoir un autre intérêt : un écran plus grand par exemple sur cette carte-ci ou celle-là, qui peuvent aussi bénéficier d'autres périphériques sympathiques, genre un codec audio ...)
Pour aller plus loin sur le sujet des performances mémoire, je me demande comment étaient chargés les assets graphiques et sonores dans le jeu d'origine, et s'il y aurait un intérêt à faire un chargement dynamique là aussi. Pour les sons (sfx), je pense que ça a clairement un intérêt, parce que même s'ils sont à un samplerate assez faible, ça serait bien d'éviter de les avoir en permanence en RAM. Pour les assets graphiques je ne sais pas trop. On pourrait se dire qu'au chargement du niveau on checke quels sont les textures et sprite requises, et qu'on ne charge que celles-là, mais je doute que ça soit vraiment faisable sans modifier radicalement le code d'origine, et je ne suis pas persuadé qu'on gagne tant que ça, car ça suppose que les niveaux utilisent peu d'assets. Et quand à les chager dynamiquement en temps-réel, c'est-à-dire au fur et à mesure qu'on en a besoin pour afficher un frame ... Hum, ça me semble overkill. Mais ptet ça a du sens ? En tous cas ça ferait sauter quasiment toutes les limitations, car on ne chargerait que ce qu'on a besoin pour la frame à afficher en cours (à moins d'avoir un design de niveau qui oblige à afficher plein d'assets en même temps sur la même image, c'est peu probable - quoique avec certains slaughterWADs ...). Bon, là on est dans le suplice des diptères.
Le vrai gros sujet, à mon avis, c'est la musique. Parce qu'on parle quand-même de faire du General MIDI sur un STM32, donc un synthétiseur polyphonique multi-timbral. Ce n'est absolument pas trivial, c'est même un projet à lui tout-seul
Mais ... Ben ça m'intéresse quand-même, parce que la synthèse ça me parle Mais c'est vraiment un gros truc.
l'un des sous-sujets qui va avec c'est de savoir si on fait de la synthèse algorithmique (ce qui va demander beaucoup de temps de dev, à moins de trouver des algos déjà-faits) soit on fait des wavetables. Dans le deuxième cas (plus facile à mettre en oeuvre à mon avis), il va falloir stocker les wavetable quelque part, et donc éviter de remplir la RAM avec, donc il y a de l'archi à faire.
Oublions un instant la musique. Si on arrive à faire sauter la limitation sur la taille du WAD, ça voudra dire qu'on pourra charger d'autres fichiers que doom1.wad. De base, Chocolate Doom, comme beaucoup de sourceports, va chercher les IWADs "officiels" connus : Doom shareware, Doom retail, Doom 2, Heretic, FreeDoom ... On pourrait se limiter à ça, mais j'aimerais bien pouvoir charger des WADs custom (yen a quand-même plein de bien qui sont compatibles avec les sourceports vanilla), mais pour ça il faut pouvoir les sélectionner. Par ordre croissant de confort d'utilisation:
- Écrire le nom du fichier dans le code en dur et re-compiler à chaque fois
- Écrire le nom dans un fichier de config dans la clé USB (c'est comme ça que marchent la plupart des sourceports actuels)
- Faire un explorateur de fichiers
La troisième solution est bien Roxxor mais bien user-friendly, et étonnamment je n'ai pas trouvé d'exemple d'explorateur de fichier "standalone" pour STM32. Alors soit j'ai mal cherché, soit c'est considéré comme inutile ou overkill. En tous cas ça me surprend car il y en a un dans le programme d'exemple qui est chargé d'origine dans la carte. Bon, ben si je me mets sur ce sujet je commencerais par dépiauter cet exemple.
Gros gros gros sujet. J'ai vraiment très envie de bosser dessus. Et j'ai vraiment très pas le temps.
Fuuuuuuu ...
Quelques liens intéressants si le sujet vous intéresse :
https://doomwiki.org/wiki/Entryway
Un tutoriel sur le fonctionnement du moteur
Le blog de Fabien Sanglard, une mine d'or !
https://www.doomworld.com/
Le repo Github de Chocolate Doom
Le repo Github du code Doom tel que publié par John Carmak en 1997. Ses notes sont assez marrantes d'ailleurs.
DOOM !!
DOOOOOOOOOOM !!!
Je suis fasciné par ce jeu, son gameplay, son histoire, sa technique ... Et il se passe encore plein de choses autour de ce vénérable, culte et révolutionnaire titre sorti en 1993, dont le code source du moteur a été publié en open-source en 1997, et dont la communauté est encore très active. La publication en open-source a ouvert la voie à une multitude d'évolutions et d'expérimentations, allant de la simple adaptation ou amélioration au portage sur à peu près n'importe quoi : calculatrice programmable (les vraies grosses un peu modernes, je ne parle pas des knock-off sur Ti-83+), consoles de salon, distributeur de billets (no joke), tableau de bord de voiture (no joke !!), écran d'imprimante jet d'encre (no joke !!!), oscilloscope numérique (:|), thermostat (wtf ?) barre OLED de MacBook Pro (non mais wtf Doom en 640 x 12 ???) ...
If it exists and has a microprocessor and a screen, it can run Doom !
Mon moi du passé lointain (2 mois, une éternité ...) avait laissé à l'intention de mon moi du passé récent (2 semaines) un onglet ouvert sur le navigateur : https://github.com/floppes/stm32doom
Doom (plus exactement Chocolate Doom) pour carte d'évaluation STM32F429. Carte dont je dispose (qui n'est pas chère d'ailleurs, on la touche à ~$30) ... Ho ho ho ...
J'ai testé : ça marche, c'est ... dingue ! Et tellement cool
Par contre il y a quelques limitations (le portage a été fait par un mec tout seul en 2015), que je suis bien motivé pour essayer de faire sauter ou contourner:
- Pas de son.
- Pas de musique.
- Contrôles de merde. Le tactile, sur du FPS, comment dire ... Il me semble impératif de faire honneur à ce jeu en permettant d'y jouer avec les contrôleurs recommandés par la PC Gaming Master Race : le clavier et la souris !
- doom1.wad only. L'intégralté du WAD est chargée dans la RAM externe de la carte dévaluation, qui fait 8Mo. De fait, cela limite la taille du WAD à 8Mo, et dans les WAD "courants", il n'y a que celui de l'épisode 1 shareware (doom1.wad donc) qui rentre (il fait ~4Mo).
- Pas de sélection du WAD au démarrage, le nom du WAD à charger est écrit en dur dans le code.
Pour le son, il faut dire qu'il n'y a pas de sortie audio sur cette carte, mais ça peut s'arranger, il suffit de faire du PWM ou du PDM sur une sortie, voire du DAC (les DAC sur les STM32 tournent à 50kHz, pour pouvoir générer du 48kHz). Par contre, il faut faire un driver pour ça. Idem pour la musique, avec une contrainte supplémentaire : la musique est en MIDI, cela va de soi. Ce qui sous-entend qu'il faut interpréter les fichiers MIDI, et surtout synthétiser les sons des instruments Là on est pas dans le simple ...
Pour le contrôleur il y a deux approches : ajouter le support clavier souris, ou faire un contrôleur DIY spécifique. Pour le support du clavier / souris, il n'y a qu'un seul port USB (micro) en host sur la carte (et de toutes façons il n'y a qu'un seul USB possible en host sur cette gamme de STM32, que je sache). Donc il faut de toutes façons multiplexer, donc utiliser un HUB. Or, le support des HUB n'est pas natif dans les libs / stack USB "standard". En tous cas pas dans les couches ST, utilisées ici. Certaines stack implémentent le support des HUB, mais la grande majorité sont des libs non-free et payantes. La seule free que j'ai vu c'est emCraft. Je n'ai aucune idée de ce que c'est et de ce que ça vaut. Par contre, après avoir cherché un peu sur les forums, il semblerait qu'ajouter le support des HUBs USB soit loin d'être complexe, et que ça prendrait ~400 lignes de code, ce qui est non-négligeable, mais tout à fait raisonnable.
Pour un contrôleur DIY, je m'inspirerait bien du contrôleur que Roul et 3ds ont fait pour la brodeuse
Par contre pour la souris ... je pense qu'il faut une vraie souris.
Pour l'interface, soit on fait un contrôleur USB qu'on multiplexe (cf point précédent) soit on le connecte à des GPIO ou un bus de com du MCU.
Pour la RAM ... Vaste sujet. plusieurs options s'offrent à nous.
- Porter le projet sur une carte qui a plus de RAM (en Discovery yen a pas, même sur les cartes d'évaluation "chères")
- Ajouter une RAM supplémentaire (SPI ?)
- Changer la gestion du chargement des datas
Ce dernier point me semble plus intéressant. Il faut noter que 8Mo de RAM, en 1993 ('back in the days' comme on dit), c'était assez standard dans les PCs. Par contre, Vanilla Doom (et donc Chocolate Doom) sont des sourceports plus récent, dont l'objectif est de reproduire l'expérience de jeu la plus proche de l'original, tout en rendant l'utilisation simple sur les OS récents. Donc, comme beaucoup de sourceports, ils tirent parti des ressources disponibles sur les PCs récents, et entre autre la RAM illimitée (en comparaison de 93). Donc, à mon avis, le fait qu'il charge l'intégralité du WAD dans la RAm est un choix de design du sourceport utilisé, choix fait par commodité pour les PCs desktop, mais contraignant ici, vu que l'on est sur une plateforme donc la quantité de mémoire disponible est similaire aux PCs de 93. Par contre niveau puissance de calcul, on est à 180MHz, looooiiin devant les 486SX33 de l'époque et je ne parle pas des temps d'accès, vitesse des bus de com' ... Donc je pense qu'il serait pertinent de charger dynamiquement les datas dans la RAM, pour limiter le taux d'occupation à un instant t. Et je suis persuadé que c'est ce qu'ils font dans le moteur d'origine, donc il va falloir que je checke ça. Par exemple, garder en mémoire l'intégralité des niveaux n'a pas de sens, vu qu'on ne joue que dans un seul niveau à la fois. Donc quand on finit un niveau il faudrait vider les données du niveau qu'on vient de finir, et mettre la place les données du niveau suivant avant de le lancer.
(A noter que le portage sur d'autres cartes peut avoir un autre intérêt : un écran plus grand par exemple sur cette carte-ci ou celle-là, qui peuvent aussi bénéficier d'autres périphériques sympathiques, genre un codec audio ...)
Pour aller plus loin sur le sujet des performances mémoire, je me demande comment étaient chargés les assets graphiques et sonores dans le jeu d'origine, et s'il y aurait un intérêt à faire un chargement dynamique là aussi. Pour les sons (sfx), je pense que ça a clairement un intérêt, parce que même s'ils sont à un samplerate assez faible, ça serait bien d'éviter de les avoir en permanence en RAM. Pour les assets graphiques je ne sais pas trop. On pourrait se dire qu'au chargement du niveau on checke quels sont les textures et sprite requises, et qu'on ne charge que celles-là, mais je doute que ça soit vraiment faisable sans modifier radicalement le code d'origine, et je ne suis pas persuadé qu'on gagne tant que ça, car ça suppose que les niveaux utilisent peu d'assets. Et quand à les chager dynamiquement en temps-réel, c'est-à-dire au fur et à mesure qu'on en a besoin pour afficher un frame ... Hum, ça me semble overkill. Mais ptet ça a du sens ? En tous cas ça ferait sauter quasiment toutes les limitations, car on ne chargerait que ce qu'on a besoin pour la frame à afficher en cours (à moins d'avoir un design de niveau qui oblige à afficher plein d'assets en même temps sur la même image, c'est peu probable - quoique avec certains slaughterWADs ...). Bon, là on est dans le suplice des diptères.
Le vrai gros sujet, à mon avis, c'est la musique. Parce qu'on parle quand-même de faire du General MIDI sur un STM32, donc un synthétiseur polyphonique multi-timbral. Ce n'est absolument pas trivial, c'est même un projet à lui tout-seul
Mais ... Ben ça m'intéresse quand-même, parce que la synthèse ça me parle Mais c'est vraiment un gros truc.
l'un des sous-sujets qui va avec c'est de savoir si on fait de la synthèse algorithmique (ce qui va demander beaucoup de temps de dev, à moins de trouver des algos déjà-faits) soit on fait des wavetables. Dans le deuxième cas (plus facile à mettre en oeuvre à mon avis), il va falloir stocker les wavetable quelque part, et donc éviter de remplir la RAM avec, donc il y a de l'archi à faire.
Oublions un instant la musique. Si on arrive à faire sauter la limitation sur la taille du WAD, ça voudra dire qu'on pourra charger d'autres fichiers que doom1.wad. De base, Chocolate Doom, comme beaucoup de sourceports, va chercher les IWADs "officiels" connus : Doom shareware, Doom retail, Doom 2, Heretic, FreeDoom ... On pourrait se limiter à ça, mais j'aimerais bien pouvoir charger des WADs custom (yen a quand-même plein de bien qui sont compatibles avec les sourceports vanilla), mais pour ça il faut pouvoir les sélectionner. Par ordre croissant de confort d'utilisation:
- Écrire le nom du fichier dans le code en dur et re-compiler à chaque fois
- Écrire le nom dans un fichier de config dans la clé USB (c'est comme ça que marchent la plupart des sourceports actuels)
- Faire un explorateur de fichiers
La troisième solution est bien Roxxor mais bien user-friendly, et étonnamment je n'ai pas trouvé d'exemple d'explorateur de fichier "standalone" pour STM32. Alors soit j'ai mal cherché, soit c'est considéré comme inutile ou overkill. En tous cas ça me surprend car il y en a un dans le programme d'exemple qui est chargé d'origine dans la carte. Bon, ben si je me mets sur ce sujet je commencerais par dépiauter cet exemple.
Gros gros gros sujet. J'ai vraiment très envie de bosser dessus. Et j'ai vraiment très pas le temps.
Fuuuuuuu ...
Quelques liens intéressants si le sujet vous intéresse :
https://doomwiki.org/wiki/Entryway
Un tutoriel sur le fonctionnement du moteur
Le blog de Fabien Sanglard, une mine d'or !
https://www.doomworld.com/
Le repo Github de Chocolate Doom
Le repo Github du code Doom tel que publié par John Carmak en 1997. Ses notes sont assez marrantes d'ailleurs.