For directional light, it should be every frame. Can’t be cached well as it depends on frustum view, so you’d need to update it very often, don’t think it’s worth the performance spikes.
Although I hear you can update cascades separately, so that could be worth looking into if you’re making a large world.
For all other lights with shadows, you should set them to on enabled or on demand.
I believe on enabled should work fine, it’ll cache shadows for static objects, and do every frame for dynamic objects with the right settings. (Dynamic part is a recent update)
on enabled basically does a single tick/shadow render when that light is enabled and that’s it, so if a static object was culled, or on a very low poly LOD when it was enabled, the shadows wouldn’t look right.
You can use on demand to manage shadow updates when using something that’s view dependent, like tessellation.
With tessellation cached shadows will look wrong if you only render it once, since it’s view dependent. You’ll have to update it more often with on demand.
You could also use on demand to update once every few seconds, but not just once like “on enable” or every single frame like “every frame”.
Possibly another use case for on demand is when LODs or culling causes issues with shadows, since on enable renders shadows once the light & shadows are enabled, if you enable it from far away it’ll react differently, and it won’t update once the player gets closer.
Fun fact: When using high quality shadow filtering, the entire cost of sampling higher quality soft shadows can’t be cached, and it’s extremely expensive for lights like point/spot (even more than directional light), all the cost will be added to deferred lighting on the GPU.
It’ll still cache the shadow render, but that’s nothing compared to the sampling cost.