-
-
Notifications
You must be signed in to change notification settings - Fork 22.8k
[3.x] Pre-calculate is_visible_in_tree()
#107324
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
Conversation
Actually I can probably modify this to keep the flag in I guess this is currently doing two things, and it could be separated into two changes. |
As long as it's not exposed from Node, we can also always move it later as an optimization |
f312496
to
9c97189
Compare
Changes done. I also noticed one small bug - I had made it so that branches taken out of the tree were set to The function name is slightly confusing here, as nodes return "is_visible_in_tree" even when nodes are not in the scene tree. Still, it doesn't seem as though it gets called (very often, if at all) when outside the main scene tree. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume vi_visible
cannot be removed in favour of visible_in_tree
?
Yes it can, I address this in the notes:
Just to be clear - I mean these should be three separate PRs, as each is divisible and checkable for regressions and can be reverted if required. Changes made as requested, let me know if any other changes. |
9c97189
to
54e5949
Compare
void Spatial::_propagate_visible_in_tree(bool p_visible_in_tree) { | ||
// If any node is invisible, the propagation changes to invisible below. | ||
p_visible_in_tree &= is_visible(); | ||
|
||
// No change. | ||
if (data.visible_in_tree == p_visible_in_tree) { | ||
return; | ||
} | ||
|
||
data.visible_in_tree = p_visible_in_tree; | ||
|
||
for (int32_t n = 0; n < get_child_count(); n++) { | ||
Spatial *s = Object::cast_to<Spatial>(get_child(n)); | ||
if (s) { | ||
s->_propagate_visible_in_tree(p_visible_in_tree); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm, perhaps this should just be merged into the children iteration we're already doing for _propagate_visibility_changed
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing you meant combine with _propagate_visibility_changed()
, and that's a great idea (I had it in mind already to investigate).
Combining their propagation is a bigger project to do as a follow up I think, as I think we need to revisit the Spatial
children linked list (like probably remove it 😝 ).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed this in RocketChat; merging these is something we want to pursue, but _propagate_visibility_changed
is currently using linked list iteration. Lawnjelly look into profiling this before making changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
Thanks! |
Greatly increases the efficiency and performance is the
is_visible_in_tree()
system.Benchmarks
~6x faster in DEBUG and RELEASE with depth 10 scene tree
(repeated for the same node, so in cache, so likely the change would be faster real world)
Has to be benchmarked from c++ as gdscript is too slow to benchmark multiple calls to
is_visible_in_tree()
.Background
The 3D code for
is_visible_in_tree()
was added 14 years ago in 2ee4ac1however I long suspected it was not a good approach, as the runtime check is very expensive - it involves recursing up the chain to look for any invisible nodes. This kind of operation is very bad on modern CPUs, as it has a tendency to cause cache misses (like a linked list is bad).
As with many systems in scene graphs, there is a choice between:
In many situations Godot goes for "lazy updating", which is the cheap update, and then the expense is deferred to getting. In most cases the expensive getter is cached. Not so for
is_visible_in_tree()
. Everytime it is called, every frame, tick, it is expensive.Solutions
There are two obvious solutions here:
I think on balance this second approach makes sense. Adding / removing nodes from the tree is a relatively rare operation, as is showing / hiding, and in most cases the update will be cheap anyway as the
_propagate_visible_in_tree()
call terminates early when no change is detected._is_vi_visible()
This problem has come up before, and I'd already added a partial solution, which was the optimized
_is_vi_visible()
system. The only snag is that this only works forVisualInstance
derived nodes, and not allSpatials
, therefore we end up needing rather cumbersome checks forVisualInstance
cast_to before using it. It would be much better if we could replace this with a generic system for allSpatials
.This is what this PR does.
Rollout
As this has some potential for the odd bug, and might require rebasing a couple of (my) PRs, I figure rollout could be done in three stages:
is_visible_in_tree()
machinery for 3D (and optional regression testing code) - this PR_is_vi_visible()
and change callers to useis_visible_in_tree()
Notes
Further work
I have removed changes to move the
visible_in_tree
flag toNode
for common 2D / 3D usage, leaving that to a later PR.