Hi Babylon team,
First, the headline: we got our production kitchen configurator running on Babylon Lite, and the performance difference is dramatic — on our scenes it is roughly 10–15× faster than our Babylon.js build. Everything is immediate: no lag when placing or editing cabinets, no waiting on scene rebuilds, camera navigation stays smooth throughout. This is exactly why we want to move to Lite, and we are following the project closely — we’re very much looking forward to further additions and fixes.
Following up on the DynamicTexture thread (thanks again for PR #432, #461 and #463 — all three landed and unblocked us): we have now brought NuminaPRO, a production kitchen-furniture configurator (React + Babylon.js 9, ~60 scene-manager modules), up on Babylon Lite through @babylonjs/lite-compat.
The scene boots, cabinets assemble with edgebanding and hardware, textures apply, ~165 FPS. Getting there took a ~1000-line app-side shim of workarounds, so I’d like to report what we hit. Everything below was verified live in the running app; I can supply a minimal reproduction for any item.
A. Engine-core issues
Following up on the DynamicTexture thread (thanks again for PR #432, #461 and #463 — all three landed and unblocked us): we have now brought NuminaPRO, a production kitchen-furniture configurator (React + Babylon.js 9, ~60 scene-manager modules), up on Babylon Lite through @babylonjs/lite-compat.
It runs. The scene boots, cabinets assemble with edgebanding and hardware, textures apply, ~165 FPS. Getting there took a ~1000-line app-side shim of workarounds, so I’d like to report what we hit. Everything below was verified live in the running app; I can supply a minimal reproduction for any item.
A. Engine-core issues
1. createBox cannot make a non-uniform box. createBox(engine, size?: number) and createBoxData(size?) only accept a scalar, so MeshBuilder.CreateBox in compat does createBox(engine, options.size ?? options.width ?? 1). A 598×18×530 panel becomes a 598³ cube. Measured: bottom bounds were ±0.299 on all axes, leftSide ±0.009 on all axes — our entire cabinet rendered as a solid block. This is the single highest-impact item for us; a non-uniform box is the most-used primitive in Babylon. Workaround: take createBoxData(1), scale positions per axis, upload via VertexData.applyToMesh.
2. glTF/GLB cannot be loaded from a blob: URL. loader-gltf/load-gltf.ts:254 derives a directory base for any string source: new URL(".", new URL(source, location.href)). That is impossible for an opaque blob: URL → TypeError: Failed to construct 'URL': Invalid URL. We fetch private models through an authorized proxy, decrypt them and wrap the bytes in a blob URL, so all hardware (hinges, legs, fasteners) failed to load. fetchGltfAsset already accepts ArrayBuffer/Blob (self-contained, baseUrl = ""), so skipping base derivation for blob:/data: sources should be a small fix. The comment four lines below already claims blob URLs are supported.
3. A mesh’s material group is bound once at addToScene and never recomputed. scene/scene-core.ts:370-401 selects the build group from mesh.material._buildGroup at add time. Our room builder creates walls with a Standard material and assigns the real PBR material a moment later; the mesh stays in the Standard group, so buildStandardMeshRenderables runs writeStdMaterialData on PBR props and dies at standard-pipeline.ts:376 (data[0] = dc[0], diffuseColor undefined). registerScene rejects → _built stays false → startEngine never runs → black viewport with no error anywhere (see item 8). Lite already has the material-swap machinery; re-grouping on material change (or guarding the write) would close this.
4. rebuildSingle reuses a shader context built for a different material. pbr-renderable.ts:253 imports the gamma template only when hasGammaAlbedo is true at first build. If a material gains a gamma-space albedo texture later, every rebuild hits _gammaTemplate!.gammaBaseColor(...) (pbr-template.ts:374) with _gammaTemplate === null → Cannot read properties of null (reading 'gammaBaseColor') every frame, crashing the app. Our workaround is to force gammaAlbedo = false for late-assigned materials, which costs us correct colour.
5. Changing shadow casters does not re-run the PCF preload. shadow/pcf-shadow-task-hooks.ts:57-81 lazily imports the “no-color” material views, choosing which ones from the material families of the casters present at preload time. setShadowTaskCasterMeshes doesn’t re-run it, so a caster whose family appeared later crashes the render loop: createPbrNoColorMaterialView is not a function at getNoColorView → shadow-task.execute → renderFrame.
6. A shadow generator with zero casters produces an invalid frame. The 4096×4096 Depth32Float map is sampled by receivers but never written, so WebGPU rejects the whole command buffer: “usage (TextureBinding|RenderAttachment) includes writable usage and another usage in the same synchronization scope” → Invalid CommandBuffer. This is the normal state of an empty project.
7. BoundingBox world-space values. centerWorld, extendSizeWorld and vectorsWorld are undefined, and minimumWorld/maximumWorld return local values (verified: a mesh at (1,2,3) still reported min (−0.3,−0.3,−0.3)). Any code doing world-space bounds maths breaks.
8. Startup rejections are swallowed. runRenderLoop does void this._start(), so anything that throws inside _startCore leaves a black canvas with an empty console. Items 3, 4 and 6 each cost us hours for this reason alone. Surfacing that rejection (console.error, or an onEngineStartFailed hook) would be a large DX win.
B. Compat APIs our app needed and had to shim
Geometry/meshes: Mesh.setIndices() / getIndices(); setVerticesData cannot create geometry (no-ops unless the mesh already has some); Mesh.MergeMeshes (absent; we use it in 9 places); Mesh.clone() is declared never and throws; bakeTransformIntoVertices / bakeCurrentTransformIntoVertices (absent, 9 call sites); mesh.dispose() does not remove the mesh from the Lite scene; Node.setEnabled() only sets a flag and does not hide.
Materials/textures: Material.clone(name), getScene(), getActiveTextures(), freeze()/unfreeze(); textures have isReady() but no onLoadObservable — our code awaits onLoadObservable with a 15 s backstop, so every module add stalled exactly 15 s until we supplied the signal; texture assignment binds to Lite only inside Material._ensureRenderable, which is called only from addPrimitive, so a material assigned after the mesh was added never binds its textures.
Loading: AssetContainer.meshes returns a stripped LoadedMesh with no transform API, no clone, no isVisible/setEnabled, no bake — post-processing imported meshes is a very common pattern and currently impossible. Returning real Mesh wrappers would remove several of our workarounds at once.
Math/camera: Quaternion.toRotationMatrix(result) (absent — fromRotationMatrix exists); camera.inputs (absent entirely, so camera.inputs.attached.pointers — standard button remapping — throws and killed our init); wheelPrecision / angularSensibility / panningSensibility are not forwarded from the compat wrapper to the Lite camera, and attachControl captures them once, whereas Babylon.js reads them live (we set wheelPrecision right after attachControl, as Babylon allows, and it was silently ignored — zoom ran ~50× too fast).
Also: scene.meshes and scene.textures are never populated, which breaks introspection and tooling.
C. One behavioural difference worth documenting
Lite’s wheel zoom scales the step by the current radius (arc-rotate-controls.ts:286), while Babylon.js uses a constant step. Close to an object each notch becomes microscopic and the camera feels “stuck” — pulling back out takes dozens of scrolls. Not a bug, but it surprises ported apps.
Closing
None of this is a blocker for us any more — we have workarounds for all 23 items. The reason I’m writing is that those workarounds are a large, fragile layer we’d rather not maintain, and items A1–A5 in particular affect any app that builds geometry from primitives or loads assets from object URLs.
Happy to open individual issues with minimal repros for whichever of these you want, and to share NuminaPRO as a real-world test case. The perf and bundle-size wins are substantial — we want to ship on Lite.
Thanks!