VSM Cached Page mode in 5.8

Hello! We have been investigating our ShadowDepths timings and have noticed that all of our scenes show complete dynamic VSM page invalidation.

We couldn’t find anything in our scene that would cause this. Having a scene with just a cube and a directional light showed the same issue, and we have also observed the same happening in a clean 5.8 project.

Do you know why this could be happening?

[Attachment Removed]

Hello,

all of our scenes show complete dynamic VSM page invalidation

Are you seeing that the Cached Page mode shows all geometry in blue or red? If it’s blue that’s likely because r.Shadow.Virtual.UseReceiverMaskDirectional=1 (default). There’s a related discussion about that here: [Content removed] . Turning this off, you’ll likely see green (static cached) instead of blue (Pages where only the static page is cached and dynamic uncached), but the VSM Shadow Casters visualization is the intended visualization to show why and who is causing invalidations. Please let me know if this is not what you are seeing, or have further questions.

[Attachment Removed]

So essentially from 5.7+ Shadow Caster mode is to be used for investigating invalidations and it replaces the old flow of looking at the cache visualiation, correct?

Yes, that is the intention, however we’ve had feedback that it could be improved and doesn’t cover all cases the previous method covered. The old Cached Page Visualization was helpful because it gave view-dependent feedback**,** like:

  • How WPO or animation culling distances affected which pages stayed cached;
  • Visually distinguish how Nanite vs. non-Nanite objects affect the scope of shadow invalidations. How manually tweaking an object’s Bounds (for non-Nanite meshes) or the Max World Position Offset Displacement in materials(for Nanite meshes) would effect Caches visually;
  • The great feature “Invalidations fully occluded by Nanite shadows could be skipped”— was reflected in the Cached Page view;
  • The visualizationclearly reflected how different Per-object Shadow Caching Invalidation Behavior settings actually play out. For example: imagine a Skeletal Mesh (SKM) animating also alone with some WPO materials effects and happens to sit inside the shadow of a Nanite object. Right now, switching its Shadow Caching Invalidation Behavior between Rigid and Auto can lead to different numbers of invalidated pages
  • The Page Density (page size) was shown, so you could see where the shadow map was getting too fine or too coarse which is supposed to be a good uncached in screen Texels density indicator

The Shadow Caster visualization doesn’t seem to provide that same level of real-time, view-aware insight. It tells you which objects trigger cache invalidations—but not how much they impact performance, or how they affect the shadowing in practice. That missing context makes it harder to optimize effectively.

If you have additional feedback to add, we appreciate it!

[Attachment Removed]

Do you know if there plans to address it for 5.9? Or will work for this happen in UE6 branch?

We don’t have any plans currently for this in 5.9 that I can see.

could you provide more info on the r.Shadow.Virtual.DynamicRes CVars

r.Shadow.Virtual.DynamicRes.* are a family of CVars introduced for the Witcher demo that provide a new VSM throttling/dynres system that allows for trading quality for performance when the Nanite ShadowDepths gets too expensive, by biasing resolution LOD. The perf gain depends on how aggressively throttling is applied, and what the max allowed bias is.

When enabled, VSM Nanite ShadowDepths performance is monitored. When a performance threshold is reached, VSM resolution ramps down by biasing the LOD up.

The bias is determined per VSM with a heuristic based on historical nanite cluster counts, i.e. expensive shadow maps are biased more than cheap ones (when data is available).

You can see the status of throttling in the vsm stats view (r.shadow.virtual.showstats 1) and the clipmap visualization viewmode.

The performance target is set with one of two cvars:

  • r.Shadow.Virtual.DynamicRes.TimeBudgetMs: use the DynamicRes system to time ShadowDepths, and aim to get nanite shadowdepths to this value. Requires DynamicResolution to be supported and active.
  • r.Shadow.Virtual.DynamicRes.LoadBudget: estimate global ShadowDepths cost based on Nanite cluster data. Can be less accurate, but works everywhere (also in editor).

The maximum resolution bias is controlled with r.Shadow.Virtual.DynamicRes.MaxResolutionLodBias (same as the page pool pressure bias).

There are two additional cvars to control bias for directional and local lights separately (r.Shadow.Virtual.DynamicRes.MaxComputeResolutionLodBiasDirectional, r.Shadow.Virtual.DynamicRes.MaxComputeResolutionLodBiasLocal)

This system is likely most effective in scenes with lots of invalidation, as biasing may mark pages that were previously unmarked.

The system is focused on Nanite performance, as non-nanite sees less benefit from scaling down resolution, and would require a different policy.

Because this system works with smoothed historical data, it is also not designed to mitigate performance spikes (e.g. camera cut).

r.Shadow.Virtual.DynamicRes.LoadBudget: Fallback heuristic — scales bias off estimated draw load. Ignored when TimeBudgetMs is active. (Experimental) A setting of 0 is throttle off, 2 normal throttle, 1 stronger throttle, we used 1.2 - 1.5 in some scenes in the Witcher demo.

r.Shadow.Virtual.DynamicRes.MaxComputeResolutionLodBiasDirectional: As compute-time cost limits are approached, VSM resolution ramps down by biasing the LOD up. Per-type cap, directional lights. Effective = min(this, MaxResolutionLodBias). Used 0.2 in the Witcher demo in some scenes

Related CVars

  • r.Shadow.Virtual.MaxPhysicalPages: pool size, the denominator everything keys off
  • r.Shadow.Virtual.ResolutionLodBiasLocal / Directional: static artist-set bias, stacked on top of dynamic
  • r.Shadow.Virtual.AllocatePagePoolAsReservedResource (default 1) — reserved-resource pool to cut fragmentation

There hasn’t been a lot of further development on VSM DynamicRes but it was key to us not dropping frames in some scenes on the Witcher demo where we targeted 60hz.

[Attachment Removed]

Thank you! I think this is indeed what I’m seeing, however turning off this CVar made everything grey :grinning_face_with_smiling_eyes:

So essentially from 5.7+ Shadow Caster mode is to be used for investigating invalidations and it replaces the old flow of looking at the cache visualiation, correct?

[Attachment Removed]

Good to know! And I agree with all the feedback, currently I’m struggling to fully outline the optimization flow for VSMs myself.

Do you know if there plans to address it for 5.9? Or will work for this happen in UE6 branch?

And a somewhat unrelated question for VSM optimization - could you provide more info on the r.Shadow.Virtual.DynamicRes CVars? I have heard a mention of them in one of the Unreal Fest talks (don’t remember which one exactly) and I was wondering about the state of dynres scaling for VSMs in general and if we should try to utilize this as well.

[Attachment Removed]

Thanks a lot for the big write up! We will investigate usage of this for our content :blush:

I think we can close this thread, thank you a lot for the information!

[Attachment Removed]