Is there any way to really determine that everything is ready?

Hello there!

I am trying to create a callback function and a render on demand function.
I do this because the scene does not have to render at 60 Frames if nothing happens at all.

So far so good. But I still get messages from users that this and that is not shown/uncomplete etc.
and seems to highly depend on user hardware and/or internet connection.

Checking:

  isReady() {
    if (!this.scene.isReady()) return false;

    for (let tex of this.scene.textures) {
      if (!tex.isReady()) return false;
    }

    for (let mesh of this.scene.meshes) {
      if (!mesh.isReady()) return false;
    }

    return true;
  }

This doesn’t seem to be all - at least for PBR materials. I check this function in onLoad calls of objects/textures and it may return false (why though?) and I then check it again after X seconds.

Still at some events a bumpTexture/anisotripicTexture/… can be missing and is only shown at user interaction (when I finally would render the scene again).

I already have this stomach pain using a timed callback but it seems I have to render after 1sec again or what ever - even if it was fine in the first place.

I already tried events/callbacks (of scene, mesh, texture, etc.) and it was shot too soon.

Also: I found out that some properties are processed on first render - but I want to do only one render and everything should be ready then. I render only one frame!
Afterwards the process might start again after mesh import.

Additional render only on user interaction.

Any ideas on this?

1 Like

You can use scene.executeWhenReady (or scene.whenReadyAsync).

1 Like

Hi, I already used this and is was also executed before it was actually ready.

The thing is - I also found out that there are things that need one render to start being progressed so already render twice.

Additionally I need this to be executed later on again (when an object is imported)

I have tried every callback/event etc. and it was never the desired result.

I would like call scene.render only once and the first one and only frame has to be completely correct.

Hi @Kevin, could you provide more details on the items that seem to need one render to start being progressed?

Hi,

I am not 100% sure. I think it has to do something with PBR materials - but don’t hammer me on this.

The general flow is the following:

  • Open page
  • Load Textures (use onLoaded callback)
  • Load Object (use onLoaded callback)
  • Check if there are not pending textures/objects/etc
  • Check if textures ready if not, try again later
  • If everything ready, render scene and execute my ready handlers

An output of ready can look like this:

READY: tex/material/noise.metallness.png
READY: tex/material/noise.png
READY: tex/material/noise.normal.png
READY: tex/m05/polish.png
READY: tex/m05/polish.normal.png
READY: tex/m05/polish.anisotropic.png
READY: tex/m05/polish.metallness.png
READY: tex/m05/ambient.png
READY: m05.babylon
READY: tex/environment/world.env

Environment seems to be the last one always.

It’s really really hard to debug. I can only play with network throttle etc. It’s working 100% of the times for met - but not for some users. I have no specific idea.

This is my render method for state changes of the scene:

// Do a full render - incl. shadow
  render(force = false) {

    if (!force && !this.isReady()) {
      if (this.awaitingAssets.length > 0) return;

      this.scene.render(); // Trigger something?
      console.log('SCENE NOT READY => Wait');

      if (this.waitTrigger) {
        clearTimeout(this.waitTrigger);
      }

      this.waitTrigger = setTimeout(
        this.render.bind(this, false),
        this.waitDuration
      ); return;

    }

    while (this.onReadyPromises.length)
      this.onReadyPromises.pop()();

    this.light.autoUpdateExtends     = true;
    this.light.shadowOrthoScale      = 0.1;
    this.light.autoCalcShadowZBounds = true;
    this.scene.render();
    this.light.autoCalcShadowZBounds = false;
    this.light.shadowMinZ            = Math.ceil(this.light.shadowMinZ);
    this.light.shadowMaxZ            = Math.ceil(this.light.shadowMaxZ);
    this.shadowMap.refreshRate       = 1;
    this.scene.render();
    this.shadowMap.refreshRate       = 0;
    this.light.autoUpdateExtends     = false;
  }

This the render function given to run render loop:

// Render the scene if needed
  onRenderRequest() {
    if (this.camera.inertialAlphaOffset  ||
        this.camera.inertialBetaOffset   ||
        this.camera.inertialRadiusOffset ||
        this.camera.inertialPanningX     ||
        this.camera.inertialPanningY) {
      this.scene.render();
    }
  }

I hope you understand what I am doing.

It’s working fine for me. But today got a mail with missing metallness/bumpMap/anisotropic texture on load which appears magically on rotate - but as you can see, I tried my best to firstly show the object when it’s ready. I am sure it’s a pretty small thing.

Info I even start the “isReady()” calling on the next frame, so there is at least a small amount of time for what ever Babylon/WebGL is doing internally with textures.

Like this:

onAssetReady(name) {
    console.log('READY: ' + name);

    this.awaitingAssets.splice(this.awaitingAssets.indexOf(name), 1);

    requestAnimationFrame(this.render.bind(this, false));
  }

I cannot find a complete texture checkup in the scene.isReady() function on github.
Maybe something is there that is missing. Something like texture compiled or maybe the RGB split thing of the PBR textures.

It seems only to check for pending. I don’t think that’s enough for textures?

See: Babylon.js/scene.ts at master · BabylonJS/Babylon.js · GitHub

The best way to help you here would be to recreate the condition in the playground :slight_smile:

1 Like

if (!force && !this.isReady()) {

What is the isReady() function? Is it the scene.isReady function?

If it is not it should be (or at least it should be part of your isReady function), this function is doing a lot of things to ensure the resources are loaded.

1 Like

Hi, I will try to exclude only the 3D Stuff from the project and make a playground.

@Evgeni_Popov

It calls scene.isReady function + additional checks. It’s this function:

  isReady() {
    if (!this.scene.isReady()) return false;

    for (let tex of this.scene.textures) {
      if (!tex.isReady()) return false;
    }

    for (let mesh of this.scene.meshes) {
      if (!mesh.isReady()) return false;
    }

    return true;
  }

Hi Deltakosh,

I definitely found out that there is a gap at this point with metallic/anisotropy texture:

  1. Load Texture and receive loaded event
  2. Render scene
  3. Apply that texture to an existing object and immdiately call “render” => Texture is not applied.
  4. Call render some time later OR when in debugger ( then there will be async time between calls…) => it’s rendered

Something only happens when the texture is applied to the material and not before.
So maybe this is what is missing on my object. I apply the texture on demand.
So the first time the texture is applied to metial like “mat.anisotropicTexture = X” and then I call scene.render(). Then not again. I hope you understand.

Is there something that parses the image for RGB channel values? Like canvas in a worker?
Anything that would not be ready when the next line of code is “scene.render()” ?

Additionally I found out it happens on Windows Chrome but not on Chrome OSX.
Could it also be something with the graphics card?

Is there a way to create a playground with the scene NOT running in render loop automatically?
It will not work with “createScene” the way I do it - I need a “naked” js file.

You can stop the render loop in a setTimeout by calling engine.stopRenderLoop:

https://playground.babylonjs.com/#YPFIKC

Thanks!

I got it now. @Deltakosh

This one is nasty:

  • Happens in chrome/edge on Windows
  • Does not happen in firefox windows, and not chrome/safari on OSX

That’s why I always felt botherd when it got reported and I was like “what the hell…”.
It seems like blink engine and windows only… any idea?
I can understand if it not has high priority but it tackled my nerves a lot :smiley:

We can look at 51 to 67: The texture pushes loaded event, is applied to mesh and scene render is called. It’s not there. Calling scene render later, it’s used (see rendering changes).
https://playground.babylonjs.com/#YPFIKC#1

The problem is not the texture that would not be loaded (it is), it is the effect that is not recreated yet after you enable the anisotropy.

It happens because of parallel shader compilation. If you disable it, it works:

https://playground.babylonjs.com/#QTFQA7

I did engine.getCaps().parallelShaderCompile = undefined; to disable parallel compilation, I don’t know if there’s a better way to do it.

1 Like

Hi,

yeah something like that is the kind of thing I had in my mind once I started this thread :slight_smile:
At least I was able to create a demo that shows this behavior everytime. So it has something to do with shader compile.

What is the disadvantage of disabling that property?

To solve this “issue” I think the best thing in my case would be that the texture/mesh/material returns isReady() => false if the shader is not compiled yet.

Would that be a solution?

This is not a big deal. Parallel shader compilation helps to boost the initial rendering but unless you have dozens of shaders you should not see a difference

What you can do is keeping parallel shader compiling but disabling allowShaderHotSwapping on the material.

That way, the previous effect won’t be kept until the new one is compiled (the one with anisotropy enabled), it will be changed right away. And as it is not ready yet, scene.executeWhenReady can be used:

https://playground.babylonjs.com/#QTFQA7#1

Of note is that you have to call scene.incrementRenderId(); to instruct the system that you advance to “the next frame”.

1 Like

I am fine with both solutions!

Thank you for that deep dive into what happens :slight_smile:

could i ask a question,what if i have many textures, and i want to wait all of them to ready,how can i do it?

You can have a look here for a solution using promises:

1 Like