← Retour au blog

Où en est Bloom : choix de design, avancement et la suite

Bloom a fêté ses cinq semaines cette semaine. Trois cent onze commits plus tard, nous voulions poser noir sur blanc ce que nous avons, pourquoi nous l'avons construit ainsi, et quels morceaux restent encore bruts.

Le pitch, en une phrase

Écrivez votre jeu en TypeScript, compilez-le en amont, et livrez un véritable binaire natif sur macOS, Windows, Linux, iOS et tvOS — ou un bundle WASM pour le web. Pas d'Electron, pas de WebView, pas de runtime JavaScript embarqué dans votre jeu livré.

Cette phrase a fait beaucoup de travail. C'est aussi la raison pour laquelle la plupart de nos premières décisions de design ressemblent à ce qu'elles sont.

Pourquoi TypeScript

Nous n'avons pas choisi TypeScript parce que nous adorons JavaScript. Nous l'avons choisi parce que c'est le langage à typage statique le plus utilisé au monde, doté d'un système de types structurel, d'un écosystème d'outils énorme, et d'une syntaxe qui ne fait fuir personne. La plupart des gens avec qui nous voulons faire des jeux ont déjà livré du TypeScript. Très peu ont livré du C++.

Nous voulions aussi un langage qui pouvait être compilé proprement en amont, sans traîner un ramasse-miettes et un interpréteur de bytecode dans chaque binaire livré. Cela écartait tout ce qui nécessite un runtime lourd (Python, C#, JS-le-langage). TypeScript — moins ses parties dynamiques par défaut — s'est révélé être un compromis idéal.

Pourquoi Perry

Perry est le compilateur en amont qui transforme votre TypeScript en code natif. Le chemin TS-vers-binaire est ce qui nous permet de promettre “aucun surcoût d'exécution.” Votre jeu devient un binaire unique qui appelle un cœur Rust via une ABI C stable. Pas de V8, pas de Bun, pas de JIT.

Choisir Perry nous a permis d'être impitoyables sur la surface de langage que nous exposions. L'API de Bloom, ce sont des fonctions et des interfaces simples — pas de classes, pas de décorateurs, pas de proxies, pas de eval. Si une fonctionnalité TypeScript ne se compile pas proprement vers un appel natif, nous ne l'utilisons pas dans l'API. Le résultat : une API qui tient sur un aide-mémoire et un build qui tient dans la tête.

Pourquoi wgpu et non quatre renderers sur mesure

Notre premier réflexe a été d'écrire un renderer Metal, puis un renderer DirectX 12, puis un renderer Vulkan. Nous nous sommes raisonnés en l'espace d'un week-end. Quatre backends, ce sont quatre langages de shaders, quatre modèles de ressources, quatre surfaces de bugs, et quatre endroits où oublier le HiDPI.

À la place, tout le renderer est écrit une seule fois au-dessus de wgpu. Les shaders sont en WGSL. Nous obtenons Metal sur les plateformes Apple, DirectX 12 sur Windows, Vulkan sur Linux et Android, et WebGPU (avec un repli WebGL) dans le navigateur — depuis une seule base de code. Le coût, c'est que nous sommes liés à l'ensemble des fonctionnalités de wgpu, et que quelques choses exotiques (mesh shaders, ray tracing) sont hors de portée pour l'instant. Nous trouvons que c'est un compromis honnête pour une petite équipe.

Ce qui est réellement disponible aujourd'hui

Nous essayons de ne rien mettre sur le site marketing qui ne fonctionne pas. Voici ce qui est réel, aujourd'hui :

  • Neuf modules importablesbloom/core, bloom/shapes, bloom/textures, bloom/text, bloom/audio, bloom/models, bloom/math, bloom/physics et bloom/scene.
  • Un véritable renderer PBR — matériaux en couches façon substrate, shadow maps en cascade avec mise en cache pour la géométrie statique, tone mapping ACES/AgX, auto-exposition, bloom, profondeur de champ, motion blur, SSGI, SSAO, TAA, et une passe de netteté CAS pour les échelles de rendu fractionnaires.
  • Animation squelettique GPU — import glTF 2.0, skinning à mélange linéaire à quatre os sur le GPU, jusqu'à 128 articulations par squelette.
  • Physique Jolt — corps rigides et mous, contrôleurs de personnage, véhicules, raycasts, contraintes, callbacks de contact. La cible web utilise le repli JoltPhysics.js pour que le même code tourne dans le navigateur.
  • Six plateformes cibles — macOS, Windows, Linux, iOS, tvOS et Web. Android est partiellement câblé mais pas encore livrable.
  • Hot reload — enregistrez un shader WGSL ou un JSON de matériau et la modification est à l'écran en moins d'une seconde pendant le développement. Le code de surveillance des fichiers est retiré des builds de release.
  • Dix-sept projets d'exemple — d'un Pong de 170 lignes au chargement des scènes Intel Sponza et Bistro.

Quelques nouveautés récentes dont nous sommes fiers

Le renderer a passé un bon mois. Quelques temps forts :

  • Auto-DRS. Le renderer ajuste lui-même son échelle de rendu pour atteindre votre framerate cible, puis lance une passe d'upscale plus de netteté RCAS pour que l'image reste nette. Vous fixez un FPS cible ; le moteur fait le reste.
  • Réflexions planaires. De vraies captures de plan-miroir avec oblique-clip et repli IBL quand le budget de réflexions est épuisé. Utile pour l'eau, les sols polis et les vitrines.
  • Splat mapping par tableau de textures. Les terrains et les couches de détail peuvent maintenant lier un tableau de textures avec des mips correctes, vous pouvez donc peindre plusieurs matériaux par tuile sans payer un draw call par couche.
  • Imposter baker. Une petite CLI qui cuit des atlas d'imposteurs octaédriques pour les LOD lointains. Nous en avions besoin dès l'instant où nous avons commencé à déposer des forêts dans les scènes.
  • HiDPI multi-plateforme. Windows, Linux et Web partagent enfin la même gestion HiDPI que macOS et iOS avaient déjà. L'interface reste nette sur chaque écran.

Ce que nous ne prétendons pas terminé

Nous vous devons aussi la liste peu flatteuse :

  • Android. Le crate de plateforme existe et la majeure partie de la surface FFI est câblée, mais la glue native-activity n'est pas finie. Nous ne disons à personne de livrer Android avec Bloom pour l'instant.
  • watchOS. Les shaders compilent, le stub de plateforme est là, mais nous sommes bloqués sur le support watchOS de Perry avant que ce soit réel.
  • Géométrie virtualisée. Pas d'équivalent Nanite, pas de mesh shaders, pas de ray tracing matériel. C'est sur la roadmap, pas dans le binaire.
  • Plafond d'animation squelettique. 128 articulations par squelette, limite stricte, à cause de la taille de l'UBO. Les gros rigs nécessitent des contournements aujourd'hui.
  • Le graphe de scène est jeune. Les transformations, la visibilité, les ombres et la liaison de matériaux fonctionnent toutes. Les systèmes de requête et les passes d'optimisation plus larges n'existent pas encore.
  • Shaders utilisateur. Vous pouvez écrire du WGSL à la main et le charger via le pipeline de matériaux, mais il n'y a pas de shader graph dans le moteur ni de compilation de shader à l'exécution depuis TypeScript.

La forme de l'API

Nous avons largement emprunté à Raylib pour les parties en mode immédiat de l'API. Si vous avez écrit un jeu Raylib, la mémoire musculaire se transfère :

import { initWindow, windowShouldClose, beginDrawing,
         endDrawing, clearBackground, drawText, Colors } from "bloom";

initWindow(800, 450, "Hello Bloom");

while (!windowShouldClose()) {
  beginDrawing();
  clearBackground(Colors.RAYWHITE);
  drawText("Hello, Bloom!", 190, 200, 20, Colors.DARKGRAY);
  endDrawing();
}

Pour le travail 3D et la physique, la même philosophie s'applique : interfaces simples, fonctions pures, pas de machines à états cachées. Une caméra est un littéral d'objet. Un corps rigide est un handle. Le pipeline de rendu est configuré en appelant des fonctions dans un ordre précis, pas en enregistrant des écouteurs sur un orchestrateur invisible.

Et après

Notre liste courte pour les prochaines semaines :

  • Finir la glue native-activity Android et déclarer Android livrable.
  • Pousser le travail sur le graphe de scène au-delà du “basique” vers quelque chose de réellement utile : requêtes, raffinement du frustum culling, helpers d'instanciation.
  • Livrer une première mouture de l'intégration éditeur pour pouvoir itérer sur une scène sans redémarrer votre jeu.
  • Ouvrir le code source des jeux d'exemple, dont une démo un peu plus grosse que Pong.
  • Documenter le pipeline de compilation Perry en détail. Plusieurs personnes ont posé la question.

Essayez-le

Bloom est open source sous licence MIT. Le dépôt complet est sur GitHub. Si vous voulez suivre sans forker, la doc propose un démarrage rapide en 12 lignes, et la page vitrine liste ce que nous construisons nous-mêmes avec.

Quoi qu'il en soit : merci d'y avoir jeté un œil. Nous continuerons à publier ici à mesure que de plus gros morceaux atterriront.