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.2] Various light culling fixes #46694

Merged
merged 1 commit into from
Mar 8, 2021

Conversation

JFonS
Copy link
Contributor

@JFonS JFonS commented Mar 5, 2021

GLES3

This commit makes it possible to disable 3D directional lights by using the light's cull mask. It also automatically disables directionals when the object has baked lighting and the light is set to "bake all".

While these changes work fine, they have some downsides due to the current structure of the GLES3 renderer:

  • Object sorting is done only once for all directional lights, so there is no way to sort the enabled/disabled objects for each light.
    This results in more GL state changes and can have some performance hit (only happens if directional lights affect some objects and not others).

  • Environment lighting is always done in the first pass, regardless of the corresponding directional light being enabled or not.
    This means in some cases there will be a first pass with env light only (disabled light) and a second pass with an enabled light only, instead of merging the two passes in a single one.

GLES2

Added a check for the light cull mask, since it was previously ignored.

Fixes #46332. Fixes #19438.

@JFonS JFonS added this to the 3.2 milestone Mar 5, 2021
@JFonS JFonS requested a review from a team March 5, 2021 14:40
@clayjohn
Copy link
Member

clayjohn commented Mar 5, 2021

Object sorting is done only once for all directional lights, so there is no way to sort the enabled/disabled objects for each light.
This results in more GL state changes and can have some performance hit (only happens if directional lights affect some objects and not others).

When you say "more GL state changes" you mean "unnecessary GL state changes right"? You aren't increasing the number of GL state changes by using the light mask, you just aren't necessarily decreasing state changes resulting in unnecessary changes, right?

@JFonS
Copy link
Contributor Author

JFonS commented Mar 5, 2021

The concept of necessary and unnecessary is a bit fuzzy...

Before these changes all directional lights affected all objects, so there were no state changes triggered by using a cull mask.

If we merge this PR, certain lights can be culled for certain objects, so even in the same directional pass, the state can change.

At the time of writing the commit message I thought this would result in a bunch of GL state changes, since there is no way to correctly sort the objects for all lights at once. But after your comment I realized we just need to sort the objects respect to the first light in the list, since the rest of passes (the additive ones) just skip culled lights.

So I will take a look at adding this sorting and the state changes should be minimized.

We still don't solve the issue with the first pass always being in charge of the environment lighting, but in this case performance will stay the same as before, It's just a missed optimization opportunity.

@clayjohn
Copy link
Member

clayjohn commented Mar 5, 2021

I think I misunderstood you before. You are talking about the USE_LIGHT_DIRECTIONAL state changing between draw calls. Indeed, it would lead to wastefully changing state when the cull_mask is used for the first directional light in the scene. Once you add in the sorting for that, this should be good to go!

We still don't solve the issue with the first pass always being in charge of the environment lighting, but in this case performance will stay the same as before, It's just a missed optimization opportunity.

I agree. I don't think this can be easily solved with the current architecture in 3.2. I wouldn't worry about it

@JFonS JFonS force-pushed the fix_directional_disabling branch from 9bf31db to 5b23482 Compare March 6, 2021 21:19
@akien-mga
Copy link
Member

The regression test project + sanitizer found a heap-use-after-free, see logs in https://github.com/godotengine/godot/pull/46694/checks?check_run_id=2047848252

Here's what the CI runs if you want to try it locally:

- name: Compilation
env:
SCONS_CACHE: ${{github.workspace}}/.scons_cache/
run: |
scons tools=yes target=debug use_asan=yes use_ubsan=yes
ls -l bin/
# Download and test project to check leaks and invalid memory usage.
# CI has no audio device, so use the Dummy audio driver to avoid spurious error messages.
- name: Importing and running project project
run: |
wget2 https://github.com/qarmin/RegressionTestProject/archive/3.2.zip
unzip 3.2.zip
mv "RegressionTestProject-3.2" "test_project"
echo "----- Open editor to check for memory leaks -----"
DRI_PRIME=0 xvfb-run bin/godot.x11.tools.64s --audio-driver Dummy -e -q --path test_project 2>&1 | tee sanitizers_log.txt || true
misc/scripts/check_ci_log.py sanitizers_log.txt
echo "----- Run and test project -----"
DRI_PRIME=0 xvfb-run bin/godot.x11.tools.64s 30 --video-driver GLES3 --audio-driver Dummy --path test_project 2>&1 | tee sanitizers_log.txt || true
misc/scripts/check_ci_log.py sanitizers_log.txt

@JFonS
Copy link
Contributor Author

JFonS commented Mar 7, 2021

@akien-mga I noticed... I will take a look on monday.

Copy link
Member

@clayjohn clayjohn left a comment

Choose a reason for hiding this comment

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

Approved subject to Akien's comment about the heap use after free.

@JFonS JFonS force-pushed the fix_directional_disabling branch 2 times, most recently from 64a07d4 to bfb90ec Compare March 8, 2021 12:49
e->sort_key |= SORT_KEY_NO_DIRECTIONAL_FLAG;
// We sort only by the first directional light. The rest of directional lights will be drawn in additive passes that are skipped if disabled.
if (first_directional_light.is_valid()) {
RasterizerStorageGLES3::Light *directional = light_instance_owner.getptr(first_directional_light)->light_ptr;
Copy link
Member

Choose a reason for hiding this comment

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

I guess the heap-use-after-free is here, the saved instance must be pointing at a now freed Light.

I'm not familiar with this code but isn't it possible to use directional_lights[0] (after checking the size I guess) instead of saving the first light RID in first_directional_light?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I was doing before, and saving the RID was an attempt to fix the issue :)

Copy link
Contributor Author

@JFonS JFonS Mar 8, 2021

Choose a reason for hiding this comment

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

Not sure what's going on... I thought the RID and RID_Owner system would take care of invalidating freed objects, but it doesn't seem to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I pushed a last attempt at fixing this, but I don't think it will succeed.

The only other way I can think for fixing this is checking here:

bool RasterizerSceneGLES3::free(RID p_rid) {
if (light_instance_owner.owns(p_rid)) {
LightInstance *light_instance = light_instance_owner.getptr(p_rid);

If p_rid == first_directional_light and invalidating the stored RID in such case, but it's quite hacky...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems like it worked. I will confirm with reduz whether RIDs are meant to be used like this, because I only found one occurrence of a similar check in the same file.

GLES3 changes:
This commit makes it possible to disable 3D directional lights by using
the light's cull mask. It also automatically disables directionals when
the object has baked lighting and the light is set to "bake all".

GLES2 changes:
Added a check for the light cull mask, since it was previously ignored.
@JFonS JFonS force-pushed the fix_directional_disabling branch from bfb90ec to f24f582 Compare March 8, 2021 14:11
@JFonS
Copy link
Contributor Author

JFonS commented Mar 8, 2021

@akien-mga The use-after-free issue is fixed, so this should be good to merge :)

@akien-mga akien-mga merged commit 0e0d73c into godotengine:3.2 Mar 8, 2021
@akien-mga
Copy link
Member

Thanks!

@tavurth
Copy link
Contributor

tavurth commented Feb 9, 2022

I'm assuming this PR is not supposed to disable the shadows from affected directional lights?

If it is supposed to then there is some bug in v3.4.3.rc1.official

@Calinou
Copy link
Member

Calinou commented Feb 9, 2022

@tavurth Please continue the discussion in #43827.

@tavurth
Copy link
Contributor

tavurth commented Feb 9, 2022

@Calinou It's more that I'm confused if this PR should have fixed the issue with shadows or not. 🤔

I attached a sample project to this issue #43827 which seems relevant, but I'm wondering what this PR did

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