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

Optimize ProceduralSkyMaterial by removing uses of acos and simplifying logic #101973

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

clayjohn
Copy link
Member

@clayjohn clayjohn commented Jan 24, 2025

The changes can be clumped into three categories:

  1. Remove use of acos(), instead do all calculations using cosines instead of angles
  2. Only include the texture read from the cover texture if it is used. This was very expensive and accounts for about half the performance gains here
  3. Remove a bunch of unnecessary calculations by factoring them out or shifting them to the CPU. This made the code much easier to read, but the overall impact on performance was minor since a lot of the factorization was being done by the compiler. I suspect this will have a bigger difference on Android devices where the compiler doesn't optimize as aggressively.

The result looks almost the same and runs over twice as fast.

Performance

M2 MBP

I used a test scene with 3 4k Viewports that do nothing but render the sky.
Before: 36 FPS (27.78 mspf)
After: 52 FPS (19.23 mspf)
No sky: 75 FPS (13.33 mspf)

Therefore we can say this reduced the cost of rendering the sky (to 409640963 pixels) from 14.45 ms to 5.9 ms! That's over twice as fast. Given that this is our default sky, I think that improvement is well worth making.

Pixel 4

This used a test scene with just a camera, 4 DirectionalLights, and a scaling factor of 2
Before: 44 FPS (22.73 mspf)
After: 52 FPS (19.23 mspf)
No sky: 60 FPS (locked) (16.67 mspf)

With resolution scaling that's rendering 2160 x 4560 pixels per frame with the sky. Saving 3.5 ms on the sky alone is pretty nice. At 1x resolution scale I am locked at 60 FPS in all cases

Looking at the GPU PROFILE when rendering at native res, this PR brings saves about 0.5 ms (of 3.0 ms). Not as much improvement as I expected

Notes:

For posterity, this change was inspired by a blog post from Kostas Anagnostou above the hidden cost of certain shader functions (like acos, atan, etc.). Which led me to a lovely blog by Sebastian Lagarde on approximating acos/asin/atan to drastically reduce instruction count. I searched our codebase and started weeding out places where we use such expensive functions. ProceduralSkyMaterial is the worst offender by far.

After replacing acos() with an approximation (and seeing a nice performance boost) I realized that the shader could still be vastly improved and ended up removing acos() altogether.

TODO

  1. Test on an android device (particularly a low end device)
  2. Apply similar changes to the PhysicalSkyMaterial

…ng logic

The result looks almost the same and runs over twice as fast
@michaelharmonart
Copy link

michaelharmonart commented Jan 24, 2025

love to see performance optimizations like this coming down the pipe!

they might not be as flashy, but those of us developing for tight performance targets like standalone VR really appreciate it!

(I remember profiling some scenes a while back in renderdoc and thinking the procedural sky render was taking oddly long for what I assumed it needed to actually do, so this is great news)

@@ -65,7 +67,8 @@ float ProceduralSkyMaterial::get_sky_curve() const {

void ProceduralSkyMaterial::set_sky_energy_multiplier(float p_multiplier) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This setter must also update "sky_cover_modulate" material param

@@ -87,7 +95,7 @@ Ref<Texture2D> ProceduralSkyMaterial::get_sky_cover() const {

void ProceduralSkyMaterial::set_sky_cover_modulate(const Color &p_sky_cover_modulate) {
sky_cover_modulate = p_sky_cover_modulate;
RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", sky_cover_modulate);
RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", Color(sky_cover_modulate.r, sky_cover_modulate.g, sky_cover_modulate.b, sky_cover_modulate.a * sky_energy_multiplier));
Copy link
Contributor

Choose a reason for hiding this comment

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

sky energy multiplier is multiplying alpha, not the entire thing

Copy link
Contributor

Choose a reason for hiding this comment

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

That's no issue, see how vec4 sky_cover_texture was previously calculated in the shader vs. how it is done now. It appears equivalent to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

if you say so, just stood out to me at a glance

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.

4 participants