How do we determine if thin instances are ready to render?

We are trying to use thin instances to reduce the CPU consumption of our app. We try to not maintain a constant render loop for performance and to instead only render when things change.

With thin instances, we can’t seem to find a reliable way to determine if things are ready to render: https://playground.babylonjs.com/#02A9VX#2

In that playground, we want to be able to only call scene.render() once the thin instances are ready to appear. Is there an easy way to accomplish this?

You must use whenReadyAsync after everything has been setup so that it starts the internal checks that everything is ready. If you do that, you need a single call to this method and a single call to scene.render():

1 Like

Hmm that seems to work for the initial render, but in our case we want to update the thin instances after the mesh has already rendered initially. E.g. in this example: https://playground.babylonjs.com/#02A9VX#7

Interestingly enough, in the above example, if we try and refresh the buffers again and do a subsequent render it seems to show. Any ideas?

Actually, the buffer doesn’t need to be updated: https://playground.babylonjs.com/#02A9VX#7

In this case, in order for the thin instances to show, I need to render and whenReadyAsync twice for some reason. I’m wondering if there is a more deterministic check I can make here. Also I’m curious what internally inside whenReadyAsync is it waiting for?

        await scene.whenReadyAsync();
        scene.render();

        // UNCOMMENT BELOW and the thin instances are shown
        await scene.whenReadyAsync();
        scene.render();

I am really not a specialist of thin instances and render loops so forgive me if I’m telling crap.
But then, if you stop the render loop, shouldn’t you be resuming it? Something like that may be?

I think we can say that this behavior is by design.

That’s because we implemented an optimization to avoid checking the readiness of a material every time isReadyForSubMesh is called: we only check it if the frame has changed since the last call of the method (that is, if the current value of scene.frameId is different from the value we stored the last time isReadyForSubMesh was called).

When you do:

await scene.whenReadyAsync();

for(let i = 0; i < 3; i++) {
    let scale = new BABYLON.Vector3(1, 1, 1);
    let rotation = BABYLON.Quaternion.Identity();
    let position = new BABYLON.Vector3(i, 0, 0);
    let mat = BABYLON.Matrix.Compose(scale, rotation, position);
    sphere.thinInstanceAdd(mat, true);
}

await scene.whenReadyAsync();
scene.render();

The first await will call isReadyForSubMesh for the sphere and make sure that the material is ready if we call scene.render() right after.

However, by adding thin instances to the sphere, you are “dirtifying” the material because we have to change the attributes that are bound to the material (shader). The second await should wait until the material is ready again but it doesn’t because for the system we are still in the same frame.

So, a simple way to fix the problem is to call scene.incrementRenderId(); before the second await:

That makes a lot of sense, thanks for your help @Evgeni_Popov !

@mawa In our case we are intentionally trying to not have a render loop and to manually call scene.render() when things have changed and are ready to render.

I am trying to get to something more fine-grained that scene.whenReadyAsync (partially just for my understanding of the internals). So far, I have come up with the following: https://playground.babylonjs.com/#02A9VX#15

// clear internal caches
scene.incrementRenderId();
let ready = false;
do {
    ready = sphere.isReady(true) && engine.areAllEffectsReady();
    await new Promise(resolve => setTimeout(resolve));
} while(!ready);
  
scene.render();

Is there something better than engine.areAllEffectsReady()? how can I get just the particular effect that I care about? I think because of the way parallel shader compilation works and the effect is swapped out, the below doesn’t seem to work for me:

// clear internal caches
scene.incrementRenderId();
let ready = false;
do {
    // This does NOT work
    ready = sphere.isReady(true) && sphere.subMeshes[0].effect.isReady();
    await new Promise(resolve => setTimeout(resolve));
} while(!ready);
  
scene.render();

Is there something like the above that I can do that only checks the readiness of the effect related to the mesh I modified?

You need to call material.isReadyForSubMesh(...) and check for true as the return value for all the submeshes of your mesh if you want to be sure that the material is ready to be used without delay for rendering.

1 Like

Hmm that doesn’t seem to work for me: https://playground.babylonjs.com/#02A9VX#16

First of all, my bad, you don’t have to call isReadyForSubMesh because it is called as part of mesh.isReady(true).

Now your PG does not work as expected because we work hard not to block the rendering even if things are not ready (that’s the “shader hot swapping” feature). So if we create a new effect and this effect is not ready yet, we use the previous one (if we have one). In that case isReadyForSubMesh returns true and we render the mesh but with the previous effect. As soon as the new effect is ready, we will switch to this one, but for that to happen, isReadyForSubMesh has to be called with new frame ids, for this very reason:

So, one way to fix your PG is to call scene.incrementRenderId(); inside the do ... while. However, this is not enough because, as explained, isReadyForSubMesh will always return true even if the new effect is not yet ready. So we need a way to know if the material has been switched to using the new effect because it is now ready. You can do this by checking that the material defines are no longer dirty: isReadyForSubMesh will keep the defines dirty until the new effect is in place:

Another (simpler) method is to simply disable shader hot swapping:

No need to increment scene.frameId / check that the defines are dirty.

Thanks for the explanation, this is very helpful!

What are the practical costs of disabling shader hot swapping? Will that block the main thread while shaders are being compiled?

No, disabling shader hot swapping means that if a new effect is not ready right after it is created but needs some time (a few frames) to be ready, your mesh won’t be displayed because isReadyForSubMesh will return false during that time.

But in your case, this has no impact since you are waiting for the material/effect to be ready before displaying it. In fact, it has a little impact since you will spend a few frames in the do ... while waiting for everything to be ready.