Refresh material before freezing it and taking screenshot

Hello !
I have an issue with freezing materials.
For example I want to freeze some materials when I don’t alternate them and don’t change the meshes informations.
However sometimes I want to add some transparency temporarly so I have to unfreeze the material do my changes and when I remove the transparency freeze it again.

But after that when I take a screenshot it seems that the material is not rendered correctly like the freeze function was called to quickly.

Here’s a playground example where you can toggle transparency and freeze with the “k” key and the GUI button enables to take a screenshot :
https://www.babylonjs-playground.com/#3GPEIL#22

You can see that if we call the freeze function after a timeOut it works. But I prefer to avoid to use this as a solution.

Thank you

The problem is that the uniform buffer of the material couldn’t be updated with the latest changes when you set mat.freeze() right away. This update is done in mat.bindForSubMesh and is guarded by:

if (!ubo.useUbo || !this.isFrozen || !ubo.isSync)

It evaluates to false because every component are false:

  • !ubo.useUbo = false because we are in WebGL2. Note that in WebGL1 everything would work as expected because !ubo.useUbo would evaluate to true and so the update code would be executed
  • !this.isFrozen = false as the material is frozen
  • !ubo.isSync = false because the GPU uniform buffer is in sync with the current material data

There are several solutions but none are really satisfactory, I think @sebavan or @Deltakosh will have to say what is the right way to handle this use case.

  1. use a timeout as you did, to let time for the system to run the mat.bindForSubMesh with this.isFrozen still equal to false
  2. call yourself mat.bindForSubMesh before calling mat.freeze: https://www.babylonjs-playground.com/#3GPEIL#23:
    • you will need to call scene.resetCachedMaterial(); to make sure the check of the cache fail
    • you can pass any mesh / submesh that has this material as its material
  3. make sure ubo.isSync will evaluate to false by setting mat._uniformBuffer._needSync = true: https://www.babylonjs-playground.com/#3GPEIL#24
    • this is the easiest solution but probably the worst because it needs some knowledge of the underlying implementation and access some private properties

Even if 3. is bad in itself, I think in the spirit it is the best because it acts only on the material, whereas 2. needs you to pass a mesh/submesh that is in reality not used for the buffer update.

Maybe a material.forceRefreshData property (or any suitable name) could be added that would essentially do this._uniformBuffer._needSync = true?

However, as it is a specific use case, I’m not sure a new property should be created for that…

Thinking out loud, the easiest thing would be to add this line in the material.freeze method itself:

    public freeze(): void {
        this.markDirty();
        this.checkReadyOnlyOnce = true;
        this._uniformBuffer._needSync = true;
    }

https://www.babylonjs-playground.com/#3GPEIL#25

1 Like

@Evgeni_Popov Thank you !

It’s a bit clearer but what about calling scene.render before freezing the material ? In this example it looks like it solves the problem but maybe because it let the time to bindForSubMeshto be called ?
https://www.babylonjs-playground.com/#3GPEIL#26

I think that adding a flag or a method that makes us sure that the material will be refreshed after the instruction would be a great thing.

I will probably go with the solution 3 because I’m not really fan of calling something to get get all the meshes that uses the material then make a loop to call bindForSubMeshfor each one of them.

Yes, calling scene.render() will work too, as (among other things) it’s looping over all meshes and do all the necessary stuff to display them. It may be a bit overkill in this circumstance, however.

Note that you don’t need to call bindForSubMesh for all meshes that use this material, you need to call it only for one (anyone), as the call is meant to refresh the material, not the mesh.