← Torna al blog

A che punto è Bloom: scelte di design, progressi e prossimi passi

Bloom ha compiuto cinque settimane questa settimana. Dopo trecentoundici commit, volevamo mettere nero su bianco cosa abbiamo, perché l'abbiamo costruito così e quali pezzi sono ancora ruvidi.

Il pitch, in una frase

Scrivi il tuo gioco in TypeScript, compilalo ahead-of-time e pubblica un vero binario nativo su macOS, Windows, Linux, iOS e tvOS — oppure un bundle WASM per il web. Niente Electron, niente WebView, niente runtime JavaScript embedded nel gioco che spedisci.

Quella frase ha lavorato parecchio. È anche il motivo per cui la maggior parte delle nostre prime decisioni di design ha l'aspetto che ha.

Perché TypeScript

Non abbiamo scelto TypeScript perché amiamo JavaScript. L'abbiamo scelto perché è il linguaggio più diffuso con tipizzazione statica, sistema di tipi strutturale, un'enorme storia di tooling e una sintassi che non spaventa nessuno. La maggior parte delle persone con cui vogliamo fare giochi ha già spedito TypeScript. Pochissime hanno spedito C++.

Volevamo anche un linguaggio che potesse essere compilato ahead-of-time in modo pulito, senza trascinarsi dietro un garbage collector e un interprete bytecode in ogni binario spedito. Questo escludeva tutto ciò che richiede un runtime pesante (Python, C#, JS-il-linguaggio). TypeScript — meno le parti dynamic-by-default — si è rivelato un punto d'incontro azzeccato.

Perché Perry

Perry è il compilatore ahead-of-time che trasforma il tuo TypeScript in codice nativo. Il percorso da TS a binario è la parte che ci permette di promettere “nessun overhead a runtime.” Il tuo gioco diventa un singolo binario che chiama un core Rust attraverso una ABI C stabile. Non c'è V8, non c'è Bun, non c'è JIT.

Scegliere Perry ci ha permesso di essere spietati sulla superficie del linguaggio che esponiamo. L'API di Bloom è funzioni e interfacce semplici — niente classi, niente decoratori, niente proxy, niente eval. Se una feature di TypeScript non compila in modo pulito in una chiamata nativa, non la usiamo nell'API. Il risultato è un'API che ci sta su un foglio riassuntivo e una build che ti sta in testa.

Perché wgpu e non quattro renderer su misura

Il nostro primo istinto è stato scrivere un renderer Metal, poi un renderer DirectX 12, poi un renderer Vulkan. Ce ne siamo dissuasi nel giro di un weekend. Quattro backend significano quattro linguaggi shader, quattro modelli di risorse, quattro superfici di bug e quattro posti in cui dimenticarsi dell'HiDPI.

Invece, l'intero renderer è scritto una sola volta sopra wgpu. Gli shader sono in WGSL. Otteniamo Metal sulle piattaforme Apple, DirectX 12 su Windows, Vulkan su Linux e Android e WebGPU (con fallback WebGL) nel browser — da un'unica codebase. Il prezzo è che siamo legati al feature set di wgpu, e alcune cose esotiche (mesh shader, ray tracing) sono fuori discussione per ora. Ci sembra uno scambio equo per un piccolo team.

Cosa c'è davvero dentro oggi

Cerchiamo di non mettere sul sito di marketing nulla che non funzioni. Ecco cos'è reale, oggi:

  • Nove moduli importabilibloom/core, bloom/shapes, bloom/textures, bloom/text, bloom/audio, bloom/models, bloom/math, bloom/physics e bloom/scene.
  • Un vero renderer PBR — materiali a strati in stile substrate, cascaded shadow map con caching per la geometria statica, tone mapping ACES/AgX, esposizione automatica, bloom, depth of field, motion blur, SSGI, SSAO, TAA e un passaggio di sharpening CAS per scale di rendering frazionarie.
  • Animazione scheletrica su GPU — import glTF 2.0, linear blend skinning a quattro ossa sulla GPU, fino a 128 joint per scheletro.
  • Fisica Jolt — corpi rigidi e soft, character controller, veicoli, raycast, vincoli, callback di contatto. Il target web usa il fallback JoltPhysics.js, così lo stesso codice gira nel browser.
  • Sei piattaforme target — macOS, Windows, Linux, iOS, tvOS e Web. Android è parzialmente cablato ma non ancora pronto per la pubblicazione.
  • Hot reload — salvi uno shader WGSL o un material JSON e la modifica è a schermo in meno di un secondo durante lo sviluppo. Il codice di file-watching viene rimosso dalle build di release.
  • Diciassette progetti di esempio — da un Pong di 170 righe al caricamento delle scene Intel Sponza e Bistro.

Alcune cose recenti di cui andiamo fieri

Il renderer ha avuto un buon mese. Alcuni momenti salienti:

  • Auto-DRS. Il renderer auto-regola la propria scala di rendering per centrare il framerate target, poi esegue un upscaling più un passaggio di sharpening RCAS perché l'immagine resti nitida. Tu imposti un FPS target; al resto pensa l'engine.
  • Riflessioni planari. Veri capture di mirror-plane con clipping obliquo e fallback IBL quando il budget per le riflessioni si esaurisce. Utili per acqua, pavimenti lucidi e vetrine.
  • Splat mapping con texture array. Il terreno e i layer di dettaglio possono ora collegare un texture array con mip corretti, così puoi dipingere più materiali per tile senza pagare una draw call per layer.
  • Imposter baker. Una piccola CLI che fa il baking di atlanti octahedral di imposter per i LOD distanti. Ne abbiamo avuto bisogno nel momento in cui abbiamo iniziato a piazzare foreste nelle scene.
  • HiDPI multipiattaforma. Windows, Linux e Web condividono finalmente la stessa gestione HiDPI che macOS e iOS avevano già. La UI resta nitida su ogni display.

Cose che non fingiamo di aver finito

Vi dobbiamo anche la lista poco lusinghiera:

  • Android. Il crate della piattaforma esiste e gran parte della superficie FFI è cablata, ma il glue per la native-activity non è finito. Non stiamo dicendo a nessuno di pubblicare su Android con Bloom, per ora.
  • watchOS. Gli shader compilano, lo stub della piattaforma c'è, ma siamo bloccati in attesa del supporto a watchOS in Perry prima che sia reale.
  • Geometria virtualizzata. Nessun equivalente di Nanite, niente mesh shader, niente ray tracing hardware. È nella roadmap, non nel binario.
  • Limite dell'animazione scheletrica. 128 joint per scheletro, limite duro, a causa della dimensione dell'UBO. I rig grossi richiedono workaround oggi.
  • Lo scene graph è giovane. Trasformazioni, visibilità, ombre e binding dei materiali funzionano tutti. I sistemi di query e i passaggi di ottimizzazione più ampi non esistono ancora.
  • Shader utente. Puoi scrivere a mano WGSL e caricarlo attraverso la pipeline dei materiali, ma non c'è uno shader graph dentro l'engine né una compilazione di shader a runtime da TypeScript.

La forma dell'API

Abbiamo preso molto in prestito da Raylib per le parti immediate-mode dell'API. Se hai scritto un gioco in Raylib, la memoria muscolare si trasferisce:

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();
}

Per il lavoro 3D e di fisica vale la stessa filosofia: interfacce semplici, funzioni pure, niente macchine a stati nascoste. Una camera è un object literal. Un corpo rigido è un handle. La pipeline di rendering si configura chiamando funzioni in un ordine specifico, non registrando listener su un orchestratore invisibile.

Cosa c'è in arrivo

La nostra lista breve per le prossime settimane:

  • Finire il glue della native-activity Android e dichiarare Android pronto per la pubblicazione.
  • Spingere il lavoro sullo scene graph oltre il “basilare” verso qualcosa di davvero utile: query, raffinamento del frustum culling, helper per l'instancing.
  • Far atterrare un primo taglio dell'integrazione editor così puoi iterare su una scena senza riavviare il gioco.
  • Rendere open source i giochi di esempio, inclusa una demo un po' più grande di Pong.
  • Scrivere in dettaglio della pipeline di compilazione di Perry. Diverse persone l'hanno chiesto.

Provalo

Bloom è open source con licenza MIT. L'intero repository è su GitHub. Se vuoi seguire i lavori senza fare un fork, la documentazione ha un quick start di 12 righe, e la pagina vetrina elenca cosa stiamo costruendo noi stessi con esso.

In ogni caso: grazie per aver dato un'occhiata. Continueremo a postare qui man mano che atterrano pezzi più grossi.