How to know the cached(?) material source code when processFinalCode isn't called?

If I create a new material:

const baseMaterial = new BABYLON.PBRMaterial(newName, scene);

and define a customShaderName and processFinalCode in it:

baseMaterial.customShaderNameResolve = (...) => {
            console.log('internal customShaderNameResolve called.')
            options.processFinalCode = (type, code) => {
                   console.log('internal processFinalCode called.')
...

And then I call baseMaterial.forceCompilation(mesh, (material)...

The first time I do this, processFinalCode gets called. If I change the scene (like add a scene.environmentTexture and repeat this whole process, processFinalCode gets called again.

If I don’t modify anything in the scene, and call this process, customShaderNameResolve runs, but processFinalCode doesn’t.

In the playground I created, you can see this behavior by clicking “change texture” once, and note it adds an environment, and it logs Internally capturing vertex code. But if you click “change texture” again, it doesn’t log this. https://playground.babylonjs.com/#YPXI4C#142

Babylon is choosing not to recompile the shader, or at least not re-call processFinalCode. What I want to do here in this case is then return the cached GLSL for this material.

I tried looking in BABYLON.Effect.ShadersStore, the only key I see that might be it is defaultPixelShader, but this is not the cached GLSL, this is the unprocessed raw GLSL (like it has #include<__decl__defaultFragment> in it), so it’s not what’s being passed to the GPU for this material.

By the way, I also tried forcing a name change of the internal material by returning a unique name in customShaderNameResolve. But everything I try here fails. It tries to look up the effect in the shader store (I think) by the new name, but I don’t know the GLSL to put in the ShadersStore yet. I thought that if processFinalCode was present, it would ignore the ShadersStore entries. But in the playground you can see if you uncomment line 50, it makes a network call for the ShadersStore entries, but if I put anything in those entries, it also fails to compile.

So I’m trying to figure out, can I either:

  1. Force a compilation every time by changing the shader’s name in customShaderNameResolve, or
  2. Can I get the cached GLSL value of the material when it doesn’t compile? Is there some internal cache key I can read the GLSL from, or re-create the cache with the same key that Babylon uses internally? I can guess at it (like base it off the scene env, lights, etc) but I’m guessing Babylon has its own caching mechanism?

The compiled effects can be found in engine._compiledEffects but are not meant for user access.

You can get the shader code of any effect using effect.vertexSourceCode and effect.fragmentSourceCode. So, for a material: material.getEffect().xxx

In your case:

Note that the code you get from vertex/fragmentSourceCode has a #define SHADER_NAME XXX injection that we do after all other processing are done. So, if you reuse this code to create a new effect, there will be a second injection which will generate an error at compilation time. In the PG above, I simply comment this line to avoid this error.

1 Like

When I call getEffect() inside makeBaseMaterial - in the forceCompilation() callback, getEffect() returns null. Is there a way to get the result there?

My challenge is that I create new materials multiple times, and the new material that doesn’t call processFinalCode might not be the the last one on the mesh, it might be a few older material versions back. So using sphere.material.getEffect() might not represent the material I’m looking for.

Also, in the forceCompilation callback, calling material.dispose(true); has no effect on future compiles, it still seems to cache the material internally and not call processFinalCode again. Is this working as expected? I would have thought fully nuking a material would remove it from internal caches

I see in forceCompilation’s source there’s a new SubMesh. Is it valid inside forceCompilation to call mesh.subMeshes[0].effect.vertexSourceCode? Will this be the newly created effect, or is this the previous one on the mesh?

You have just created a new material, so it has no effect generated yet when you call forceCompilation: forceCompilation will trigger the creation of the effect.

Are you disposing the material you have just called forceCompilation for? It seems a bit strange to me… Anyway, a repro would help to understand, as calling material.dispose(true); should dispose the effect, so if a new one must be recreated later on, processFinalCode should be called.

No it’s not, because the effect may not be created synchronously: with parallel shader compilation, you can even be sure it won’t. It will be created asynchronously and subMesh.effect.vertexSourceCode will be empty. In any case, the submesh which is created is a detached submesh, it is not added to the source mesh, so you won’t have access to it.

[EDIT] Ah, I just realize that we are generally using effects at the submesh level, and that the effect retrieved by mesh.material.getEffect() is not used (it’s only used in the ShaderMaterial case, when not storing the effects at submesh level)… That’s probably why you get null when doing material.getEffect().

In your case, it should not be too much of a problem because you are dealing with a single mesh with a single submesh. So you should retrieve the effect through mesh.subMeshes[0].effect instead of material.getEffect()