Le declencheur du rapport de stats est la **feature cargo `stats`**
(`#[cfg(feature = "stats")]` dans app.rs). Le code ne lit que WSG_STATS_FORMAT et
WSG_STATS_INTERVAL. `WSG_STATS` est lu par **rien** — et il etait pose comme un
interrupteur a 15 endroits.
Le pire cas n'etait pas la doc : c'est l'exemple `instances` qui **affichait**
« With `WSG_STATS=1`, the entity mode reports ~100 000 draw calls… ». Un faux
interrupteur dans un message est lu comme une instruction, et sa faussete ne
produit aucune erreur — la doc avait raison hier, c'est le code qui a bouge.
`bench/run-wsg.sh` le posait reellement.
Corrige : 12 lignes de commande mecaniquement, 3 prose a la main (la consigne de
l'exemple devient `--features stats`, le DRAFT 12.15 et le gotcha AGENTS.md sont
mis au passe plutot qu'effaces — un gotcha qui survit au code qu'il decrit est
pire que l'absence de gotcha).
Verrou : documented_env_switches_are_actually_read_by_the_code croise les
`env::var("…")` de lib/ avec toute apparition de `WSG_…=` dans docs/, bench/ et
lib/examples/. **Vu echouer** en reintroduisant l'ancienne variable :
WSG_STATS (docs/user/effects/ssao.md:173)
AGENTS.md est exclu du perimetre, a dessein : un fichier de gotchas doit pouvoir
citer le defaut qu'il decrit — meme famille que le test de @group(4) qui doit
ignorer les commentaires sinon son propre commentaire declenche la regle.
Deux erreurs a moi consignees dans le gotcha :
- « 9 occurrences » annoncees de tete (un grep docs/ + un grep code/, jamais
sommes). Le reel etait 15, trouve en relancant UN SEUL grep sur le depot.
- le gotcha que j'ai reecrit tantot citait un nom de test qui n'existait pas
(`stats_docs_name_only_variables_the_code_reads`) : la legende inverifiable,
dans le depot qui a un gotcha exactement pour ca. Verifie a present : les deux
tests cites existent.
cargo test --workspace : 580 tests, 0 echec (579 + le verrou).
12.14 — Comparative benchmark: WSG instancing vs Babylon.js thin instances
Goal
Validate the Phase 12 acceptance criterion: same scene, same geometry, 100 000
transformations — compare frame time and draw calls against Babylon.js
thinInstanceSetBuffer.
Scene parameters (identical on both sides)
| Parameter | Value |
|---|---|
| Geometry | Unit cube (1×1×1, centered) |
| Instances | 100 000 |
| Layout | Grid, 250 columns, spacing 1.5 |
| Camera | Orbiting, radius 300, height 170, FOV 45°, near 0.5, far 5000 |
| Lighting | One directional (0, 0.6, 0.8), intensity 3 |
| Culling | Frustum per instance |
How to run
WSG side
# Interactive (window, orbit with mouse, stats on stdout)
cargo run -p wsg-lib --example instances --release
# Headless benchmark (no window, prints a stats table)
cargo test -p wsg-lib --release -- scale_benchmark_reports_a_stats_table --nocapture
The instances example prints a line like:
[WSG] 60 fps | 16.7 ms | 1 draws | 100000 inst (1 lot) | 0 ent | ...
The headless test prints a table:
vue ms/frame fps dessinées pixels soumises
0 14.23 70.3 98432 45210 100000
20 15.01 66.6 72105 38901 100000
...
── 120 frames en 1.82 s → 65.9 fps ; moyenne 85230 instances dessinées par frame
Babylon.js side
cd bench/
python3 -m http.server 8080
# Open http://localhost:8080/babylon-thin-instance.html
Or simply open babylon-thin-instance.html in a browser (requires internet for
the Babylon.js CDN).
The overlay shows FPS, frame time, and draw calls.
Expected results (reference hardware: RTX 3060, 1920×1080)
| Metric | WSG (wgpu/Vulkan) | Babylon.js (WebGL2) |
|---|---|---|
| Draw calls | 1 | 1 |
| FPS | ~60 (vsync) | ~55–60 |
| Frame time (GPU) | ~1–2 ms | ~3–5 ms |
| CPU overhead | ~0.1 ms (1 descriptor) | ~2–4 ms (matrix upload) |
Note: Babylon.js re-uploads the full 6.4 MB matrix buffer every frame
(thinInstanceSetBuffer with staticBuffer=true avoids this, but the default
path does not). WSG uploads once and only re-uploads when mark_dirty() is
called (12.11). This is the main architectural difference.
Caveats
- Not a controlled benchmark: browser vs native, different GPU drivers, different rasterizers. The goal is order-of-magnitude validation, not micro-optimization comparison.
- Babylon.js frustum culling: thin instances are culled as a whole mesh (one AABB), not per instance. WSG culls per instance (12.6). On a scene where 50% of instances are off-screen, WSG draws 50% fewer vertices.
- Resolution: the browser renders at device-pixel-ratio (often 2× on HiDPI).
Set
devicePixelRatio: 1in the Engine options for a fair comparison, or compare at the same resolution.
Files
| File | Role |
|---|---|
babylon-thin-instance.html |
Babylon.js benchmark (standalone, CDN) |
README.md |
This file |