When is customShaderNameResolve called?

If this is the case, do you know what prevents it from getting called infinitely before the image is added, and what about adding the image triggers it?

Also I thought we were allowed to modify defines in customShaderNameResolve? Shouldn’t that be a safe operation?

I’m still seeing an infinite loop of customShaderNameResolve in my own project (not playground) when removing the defines set. I’m not sure what else is triggering it.

What’s confusing to me is why the material is getting recreated(?), and how my code is supposed to handle that case. Is there a way for my code to know it shouldn’t keep modifying the defines when customShaderNameResolve is called? And if my code bails in customShaderNameResolve and doesn’t set processFinalCode etc, will that blow away the results of any previous customShaderNameResolve for the same material? Or does it preserve them?

No, that’s why I will need to debug it to understand what’s going on.

Ok, got it.

When setting a new texture:

  • the material is dirtyfied as expected
  • customShaderNameResolve is called and the value of AMBIENTDIRECTUV is updated
  • it means the shader code is not the same as previously and a new effect needs to be created with the new shader code
  • since parallel shader compilation is enabled, the creation of the effect is done asynchronously and the effect is not ready in the same frame than the texture was modified. It means that the system will keep the current effect for the material (to prevent the mesh from not rendering for the current frame) and will dirtyfy the material to make sure that we check for the new effect readiness in the next frame and that we can switch to the new effect as soon as possible. But because the material is dirty, customShaderNameResolve will be called in the next frame, and because AMBIENTDIRECTUV is updated again with a new value, the effect we were waiting for to be ready is no longer relevant and we have to create a new effect. This effect won’t be ready in the current frame because of the parallel compilation, and we now loop through the same processing as just described and end up recreating a new effect at each frame.

So, the fix is to not update AMBIENTDIRECTUV in customShaderNameResolve.

Note that the effect is not recreated at each frame from the beginning because even if the new effect is not ready yet, we can’t keep the current effect because there isn’t one yet. In this code:

if (this.allowShaderHotSwapping && previousEffect && !effect.isReady()) {
    effect = previousEffect;
    defines.markAsUnprocessed();

    if (lightDisposed) {
        // re register in case it takes more than one frame.
        defines._areLightsDisposed = true;
        return false;
    }
} else {
    scene.resetCachedMaterial();
    subMesh.setEffect(effect, defines, this._materialContext);
}

it means we take the else branch (previousEffect == null) and defines.markAsUnprocessed(); is not called => the material is not dirtyfied.

Thank you @Evgeni_Popov you unblocked me to be able to start intercepting and recompiling Babylon shaders, to do things like use full shaders as normal/albedo maps: https://twitter.com/andrewray/status/1613795846545616897

(or to bypass the Twitter spam the Babylon demo is at http://frogger.andrewray.me/editor.html?example=&engine=babylon)