Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Add an option to update shadow maps less often #54516

Open
wants to merge 1 commit into
base: 3.x
Choose a base branch
from

Conversation

Calinou
Copy link
Member

@Calinou Calinou commented Nov 2, 2021

3.x version of #55000.

The new rendering/quality/shadows/update_every_2_frames project setting can be used to improve performance on low-end setups, at the cost of shadows visibly lagging behind for dynamic lights/objects when up close.

This option works best with lights that use Reverse Cull Face and a low negative bias value.

Thanks to @m4nu3lf for providing help with the implementation 🙂

I'll look into implementing a similar feature for master sometime in the future.

This partially addresses godotengine/godot-proposals#2745.

Performance comparison

Using the following testing project: test_animated_shadows_3.x.zip

OS: Fedora 34
CPU: Intel Core i7-6700K
GPU: GeForce GTX 1080
Resolution: 2560×1440

Moving DirectionalLight + 3 OmniLights + 1 SpotLight

Shadows updated every frame

Project FPS: 418 (2.3 mspf)
Project FPS: 418 (2.3 mspf)
Project FPS: 421 (2.3 mspf)
Project FPS: 420 (2.3 mspf)
Project FPS: 420 (2.3 mspf)
Project FPS: 419 (2.3 mspf)
Project FPS: 420 (2.3 mspf)
Project FPS: 419 (2.3 mspf)

Shadows updated every 2 frames

Project FPS: 507 (1.9 mspf)
Project FPS: 513 (1.9 mspf)
Project FPS: 517 (1.9 mspf)
Project FPS: 513 (1.9 mspf)
Project FPS: 514 (1.9 mspf)
Project FPS: 511 (1.9 mspf)
Project FPS: 508 (1.9 mspf)
Project FPS: 514 (1.9 mspf)

Moving DirectionalLight

Shadows updated every frame

Project FPS: 1127 (0.8 mspf)
Project FPS: 1134 (0.8 mspf)
Project FPS: 1132 (0.8 mspf)
Project FPS: 1131 (0.8 mspf)
Project FPS: 1131 (0.8 mspf)
Project FPS: 1126 (0.8 mspf)
Project FPS: 1137 (0.8 mspf)
Project FPS: 1134 (0.8 mspf)

Shadows updated every 2 frames

Project FPS: 1286 (0.7 mspf)
Project FPS: 1336 (0.7 mspf)
Project FPS: 1332 (0.7 mspf)
Project FPS: 1329 (0.7 mspf)
Project FPS: 1332 (0.7 mspf)
Project FPS: 1327 (0.7 mspf)
Project FPS: 1338 (0.7 mspf)
Project FPS: 1330 (0.7 mspf)

Moving 3 OmniLights + 1 SpotLight

Shadows updated every frame

Project FPS: 503 (1.9 mspf)
Project FPS: 505 (1.9 mspf)
Project FPS: 507 (1.9 mspf)
Project FPS: 503 (1.9 mspf)
Project FPS: 504 (1.9 mspf)
Project FPS: 488 (2.0 mspf)
Project FPS: 509 (1.9 mspf)
Project FPS: 506 (1.9 mspf)

Shadows updated every 2 frames

Project FPS: 525 (1.9 mspf)
Project FPS: 528 (1.8 mspf)
Project FPS: 530 (1.8 mspf)
Project FPS: 524 (1.9 mspf)
Project FPS: 527 (1.8 mspf)
Project FPS: 528 (1.8 mspf)
Project FPS: 523 (1.9 mspf)
Project FPS: 524 (1.9 mspf)

@clayjohn
Copy link
Member

clayjohn commented Nov 3, 2021

This is a very interesting proof of concept! I would love to see this developed into a full fledged feature. But to be mergeable, it is going to require some work, and likely larger changes.

First of all, the update logic should be split so half of the shadow maps can be updated one frame and the rest the next. Otherwise you will create a subtle jitter because every second frame runs slower.

Second, the update logic should likely be connected to distance to camera, or size in frame or some other LOD logic so that shadows near the camera are still updated every frame.

Third, directional light should be treated differently than omni lights and spot lights as it is cast over most objects in the scene while the other two are more likely to only affect a few objects. However, omni light and spot lights are more likely to move themselves.

@m4nu3lf
Copy link
Contributor

m4nu3lf commented Nov 3, 2021

This is a very interesting proof of concept! I would love to see this developed into a full fledged feature. But to be mergeable, it is going to require some work, and likely larger changes.

First of all, the update logic should be split so half of the shadow maps can be updated one frame and the rest the next. Otherwise you will create a subtle jitter because every second frame runs slower.

Second, the update logic should likely be connected to distance to camera, or size in frame or some other LOD logic so that shadows near the camera are still updated every frame.

Third, directional light should be treated differently than omni lights and spot lights as it is cast over most objects in the scene while the other two are more likely to only affect a few objects. However, omni light and spot lights are more likely to move themselves.

I think that spot/omni lights should be grouped based on the atlas split they are in. The user should then be able to specify the number of frames to skip for each of the 4 atlas subdivisions, plus one for directional lights.

As for subdividing lights into subgroups to spread the cost of the updates, that could be tricky as lights could constantly move from one atlas subdivision to another.
One easy way to do that could be by hashing their pointer (modulo the number of frames to skip) to immediately find a subgroup. But I don't know how well this would work with a few lights.

@Calinou
Copy link
Member Author

Calinou commented Nov 3, 2021

I think that spot/omni lights should be grouped based on the atlas split they are in. The user should then be able to specify the number of frames to skip for each of the 4 atlas subdivisions, plus one for directional lights.

This may add a fair bit of complexity, so in the meantime, I guess I could just make point lights update on even frames and directional lights update on odd frames. This would at least allow spreading the load evenly when using both directional light and point light shadows.

Of course, this won't allow spreading the load over frames if you only use directional shadows or only point lights with shadows (indoor scenes). It'll still help in most outdoor scenes though.

@Calinou
Copy link
Member Author

Calinou commented Nov 9, 2021

I updated this PR to stagger updates over frames by updating point light shadows in one frame, and directional light shadows in another. See OP for updated benchmarks.

@Calinou Calinou force-pushed the shadow-add-frameskip-option-3.x branch from b17db4f to 7d60d68 Compare November 9, 2021 16:14
@Calinou Calinou changed the title Add an option to update shadow maps less often Add an option to update shadow maps less often (3.x) Nov 15, 2021
This can be used to improve performance on low-end setups,
at the cost of shadows visibly lagging behind for dynamic lights/objects
when up close.

This option works best with lights that use Reverse Cull Face and a low
negative bias value.

Co-authored-by: Manuele Finocchiaro <m4nu3lf@gmail.com>
// directional lights
{
if (bool(GLOBAL_GET("rendering/quality/shadows/update_every_2_frames"))) {
// Toggle between directional and point light shadow updating every frame.
Copy link
Member

@lawnjelly lawnjelly Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technique is discouraged to be used every frame, as these string methods are not efficient.

If you look in the comment section at the top of project_settings.h, there are now two methods to only query this when project settings have changed.

From VisualServerScene, as it cannot receive signals, you probably want to cache this value, and call ProjectSettings::has_changes() and only call GLOBAL_GET if changes have occurred.

We can probably centralize this for all such project settings that need to be cached in e.g. VisualServer. If you are not sure how to do this, just remind me after such a change and I can put this in.

We may alternatively be able to use a little macro to do this and store the cached value in a static, much like the WARN_PRINT_ONCE type macros.

} else {
shadow_map_update = ShadowMapUpdate::SHADOW_MAP_UPDATE_POINT_AND_DIRECTIONAL;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is just to rotate between 3 modes, we can alternatively just cast to an int and increment then wraparound to zero.

@lawnjelly
Copy link
Member

I think as mrjustaguy hints in the master PR, one big problem might be with the non-directional lights that they update on demand, rather than every frame. So they could end up getting "stuck" with the shadow map one frame behind, or miss one important big update, and be completely wrong.

@akien-mga akien-mga modified the milestones: 3.5, 3.x Jul 2, 2022
@Calinou Calinou changed the title Add an option to update shadow maps less often (3.x) [3.x] Add an option to update shadow maps less often Jun 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants