addToScene/removeFromScene and what seems to be a bit of inconsistency

Hello,

Sorry, I am new not only with Babylon Lite/JS but Java/TypeScript in general, so I am not sure, maybe it is all fine…

But, objects are created using engine, and destroyed by removeFromScene (not removeFromEngine or something like that…). And what might seem like an innocent code could cause blank renders (GPU error: “Destroyed texture [Texture (unlabeled 1x1 px, TextureFormat::RGBA8Unorm)] used in a submit”), because removeFromScene destroyed the texture)

I understand that it is Lite etc and that addToScene shouldn’t be called I guess after scene is already running (because it is not able to introduce new materials) :

But still, I find it a bit weird, that objects are created using engine and there is no clear way to dispose of them with an engine alone (would have to call removeFromScene? Would it even work if I didn’t add it to the scene first?). It is not blocking for me or anything, just wondering…

Hi

We had the discussion about remove/add. there is an open draft PR that is a source internal discussion - feat(scene): make removeFromScene symetric with addToScene by RaananW · Pull Request #337 · BabylonJS/Babylon-Lite · GitHub

you can follow this PR for progress.

Hello,

Could you consider making registerScene[WithShadowSupport]/unregisterScene symmetric too?

(or to be able to rebuild somehow…)

How do I remove/add lights otherwise?

Best regards,

Pavel

Let me check!

Fair ask, but registerScene / unregisterScene are not the pair you think they are:

unregisterScene(scene) only detaches the scene from its surface’s render list. It’s a “stop drawing this” switch — nothing is rebuilt, nothing is released.
registerScene / registerSceneWithShadowSupport do the one-time heavy lift: drain the deferred builders, compile the shader permutations, allocate the bind groups, sort renderables, build the frame graph. Once a scene is built, calling them again is essentially just “re-attach”: the build does not run a second time.

That’s very much on purpose in Lite: anything that can be resolved once at build time is baked so the render loop stays allocation-free. The trade-off is that part of the topology is frozen at that point. Your ground’s renderable captured, at build time, both the shadow bind group (pointing at that generator’s map) and the shader permutation, including the index of the shadow-casting light in scene.lights . Remove the spot and add a new one and none of that is updated: the receiver keeps sampling the old generator and looks at the wrong light slot: hence the broken result. Re-registering can’t repair it, because the build step is a no-op once the scene has been built.
(The Destroyed texture (1x1 RGBA8Unorm) used in a submit from your first post is a different path: a refcounted material texture released on mesh removal. I’m tracking that one separately.)

We already have a few ones fully dynamic though:

• Light properties: position, direction, intensity, color, angle… The lights UBO is refreshed whenever one of them changes, and the shadow generator follows its light.
• Adding/removing plain (non shadow-casting) lights: the light count is a uniform (up to MAX_LIGHTS , 16 by default, raisable via setMaxLights ).
• The caster list: setShadowTaskCasterMeshes(sg, [...]) can be called at any time.

So in your playground, the cheapest fix is to keep the same spot + generator and just move it:

spot.position.set(0, 4, 0);
spot.direction.set(0, -1, 0);

No remove/add, no re-register, and the shadow map follows.

If you really need a different shadow topology (new generator, different receivers), the supported route today is a scene swap: build a fresh SceneContext on the same engine, addToScene your entities into it, then disposeScene(old) . Mesh GPU buffers are refcounted per owning scene, so as long as the new scene has claimed them first they aren’t freed. Same pattern as Transitioning Scenes in Lite.

That said, your underlying point stands: “rebuild this scene in place” is a genuinely missing primitive, and the current failure mode (a destroyed texture instead of a clear error) is not acceptable. I’d rather add an explicit rebuild/invalidate step than turn unregisterScene into a full teardown. I’ll work on a PR :wink:

Hello,

Thank you for looking into this. It is not important for me or anything - beginners errors i guess (got confused by naming). Perhaps you could also take a look at two playground scenes in my first post (above), one produces GPU error (and black canvas, not just an object, should be probably even more unacceptable :wink: ), and cube which is not displayed at all because there were no such material type when registered scene() - quite serious consequences for seemingly innocent code (to beginner as me at least)…

Ok I did not see them earlier lol

And you found 2 good bugs :slight_smile:

PG 1: Destroyed texture used in a submit

removeFromScene tears down the mesh’s GPU state synchronously, and you call it from onBeforeRender (mid-frame). It releases the material’s refcounted textures, the count hits zero, and the texture dies while the frame still referencing it is being recorded. Every other rebuild path in Lite defers that teardown until after the frame submits; mesh removal is the one that doesn’t.

It also breaks make-before-break in your case: the clone shares the material (and texture) with the original, so the refcount drops to zero before the clone’s renderable is built. Deferring the teardown fixes both : your snippet then works as written.

PG 2 : the box that never shows up

Not really about calling addToScene late, and not PBR-specific. A runtime-added mesh is queued for a renderable rebuild, but the rebuild only runs if a group for that material already exists and has been built. You had no PBR mesh at registerScene time, so the group is brand new and unbuilt and the queued mesh is silently dropped. No renderable, no error. That’s why your commented-out line works.

Lite already has a runtime build path that handles a never-built group (thin instances use it); mesh add just isn’t routed through it. Fix is to route it there : the mesh shows up a frame or two later, and failures surface as real errors instead of silence.

Workarounds for now

• PG 1: don’t remove meshes from inside the render loop : stopEngine() → remove/add → startEngine() , or move/scale the mesh out of sight.
• PG 2: create one mesh per material family before registerScene , or do a scene swap.

And agreed: a silent black canvas or an invisible mesh is not an acceptable failure mode. Both go into the PR (coming as soon as possible)

So I owe you 2 PRs ;D

feat(scene): rebuild renderables when light/shadow topology changes by deltakosh · Pull Request #457 · BabylonJS/Babylon-Lite

fix(scene): defer mesh removal teardown and build runtime material families by deltakosh · Pull Request #461 · BabylonJS/Babylon-Lite