Is it intentional that the shadow map refresh rate is ignored under FAST Snapshot Rendering?

I previously reported an error that occurred under these conditions here: Error when using a shadow generator with a refreshRate set in Snapshot Rendering

Since that bug was fixed, the error no longer occurs, but the refreshRate setting doesn’t seem to have any effect.

repro: Babylon.js Playground

Is this behavior intentional? If so, does it mean that the optimization technique of “lowering the shadow calculation frequency to improve performance” cannot be used alongside FAST Snapshot Rendering?

If there are any alternative workarounds or methods to improve performance in this scenario, I would love to know.

Thank you for maintaining this great framework!

Yes, it’s intentional, because fast snapshot rendering simply renders what have been recorded when the mode has been enabled: if the shadow map is not generated at record time, it will never be generated at replay time. That’s why the refresh rate setting is not taken into account at record time: the shadow map generation is always recorded in the bundle.

There’s no way around that, we can’t choose to run some graphics commands and not some others in the bundle generated by fast snapshot rendering mode.

Thank you for your answer!

Additional question:
Before starting SR, if we set refreshRate to REFRESHRATE_RENDER_ONCE to render the shadow map just once and then enable SR, wouldn’t it be possible to reuse that shadow map during bundle replay without having to regenerate it? (I tried this on my end, but it didn’t work.)

Yes, if the shadow map is really static, you can pre-render it before enabling FAST snapshot rendering and then override the shadow map render target so it is skipped during snapshot replay.

PG: https://playground.babylonjs.com/?webgpu#8DM2FW#1

The important part is that the shadow map is first rendered once while snapshot rendering is still disabled:

await scene.whenReadyAsync();
engine.beginFrame();
scene.render();
engine.endFrame();
sr.enableSnapshotRendering();

Then this override is applied only to this shadow generator’s render target:

const shadowMap = shadowGenerator.getShadowMap();
shadowMap.refreshRate = BABYLON.RenderTargetTexture.REFRESHRATE_RENDER_ONCE;

const objectRenderer = shadowMap._objectRenderer;
const originalShouldRender = objectRenderer.shouldRender.bind(objectRenderer);
objectRenderer.shouldRender = function () {
    if (engine.snapshotRendering) {
        return this.refreshRate !== BABYLON.RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
    }

    return originalShouldRender();
};

So once FAST snapshot rendering is enabled, this specific shadow render target is not rendered anymore if its refreshRate is REFRESHRATE_RENDER_ONCE, and the previously generated shadow map is reused.

However, this uses an internal API (_objectRenderer), so I would treat it as a workaround only. It is safe only if the shadow map can stay static: no moving shadow casters/lights, or at least no need for the shadow itself to update.

Changing Babylon.js globally to do this would be a breaking behavior change and could introduce bugs. The current behavior forces render targets to render under snapshot rendering so the render pass / bundle topology stays consistent. If Babylon started skipping REFRESHRATE_RENDER_ONCE render targets globally, some existing scenes could end up with missing render target passes or FAST snapshot replay artifacts.

Thank you for the detailed explanation and the playground!

By doing this, I was able to simulate updating shadows with animated mesh at an arbitrary rate while using SR:

Since I prefer not to rely too much on internal properties, I’m still considering whether to actually adopt this approach.

Note that accessing _objectRenderer is quite safe, we won’t ever remove this property (we may even add a public accessor for it at some point).