Improve performance of changing compound shapes when using Jolt Physics#101189
Merged
akien-mga merged 1 commit intogodotengine:masterfrom Jan 13, 2025
Merged
Improve performance of changing compound shapes when using Jolt Physics#101189akien-mga merged 1 commit intogodotengine:masterfrom
akien-mga merged 1 commit intogodotengine:masterfrom
Conversation
jrouwe
reviewed
Jan 6, 2025
bdd8f74 to
053d924
Compare
jrouwe
approved these changes
Jan 6, 2025
smix8
approved these changes
Jan 12, 2025
Member
|
Thanks! |
This was referenced May 28, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(This relates to the to-do list item in #99895 titled "Implement the deferred compound shape mentioned above".)
This PR lessens (albeit not a lot) a long-standing performance discrepancy between the Jolt Physics module and Godot Physics, where multiple additions/removals/modifications to a compound shape (i.e. an object with multiple shapes) during a single physics frame will incur a significantly heavier cost when compared to Godot Physics.
Problem
The reason for this performance discrepancy is due to several factors, but in short, there's just more work happening when adding shapes with the Jolt Physics module when compared to Godot Physics. To start with, the Jolt Physics module uses a compound shape that's better optimized for complex shape structures, called
JPH::StaticCompoundShape, which incurs a heavier cost upon creation in order to build its spatial partitioning. On top of that, we also calculate the object's mass properties and update the broadphase on any shape addition/removal/modification, unlike Godot Physics, which instead defers these things to the next physics step1.This isn't so much a problem when you're dealing with an object that hasn't yet been added to a scene tree, since we have no real reason to (and currently do not) create/commit the underlying Jolt shape at that time, but as soon as the object is added to a scene tree we have no choice but to assume that every shape addition/removal/modification is the last one before any number of operations happen that will require an up-to-date Jolt shape, bounds and/or mass properties (such as the simulation itself) and as a result we're forced to rebuild the compound shape from scratch2 with every change.
Change
The original plan for addressing this (as discussed in jrouwe/JoltPhysics#1165) was to make a custom compound shape type that acted as a lazily built compound shape, which would within itself preserve a
JPH::StaticCompoundShapeSettings(its "blueprint" if you will) and only actually create/commit theJPH::StaticCompoundShapewhen any of the lazy compound shape's methods were invoked, such as when getting any of its properties or performing a query against it (e.g. during simulation).This however proved to be somewhat messy and error-prone in practice, since we now had to (similar to Godot Physics and its mass properties update) be mindful about always committing the shape before doing anything to the object that required its shape, bounds and/or mass properties to be up-to-date.
As a result, this PR takes a more conservative approach, by simply switching between creating a
JPH::StaticCompoundShapeand the cheaperJPH::MutableCompoundShapedepending on the context, with the latter always being "upgraded" to aJPH::StaticCompoundShapeat the next physics step. This allows us to at least avoid paying for some of the cost ofJPH::StaticCompoundShapewhile also ensuring that we always have an up-to-date shape, bounds and mass properties. However, things can now also be a bit slower if you, for example, perform a physics query against an object that hasn't yet had itsJPH::MutableCompoundShapeupgraded to aJPH::StaticCompoundShape, but I would consider this a rare edge-case.The heuristic for picking between the two compound shape types is as follows:
JPH::StaticCompoundShaperight away.JPH::MutableCompoundShape, and queue it up to be upgraded to aJPH::StaticCompoundShapelater.There are also some other improvements included in this:
JPH::CompoundShapeSettings, reducing the amount of memory allocations needed.JPH::TempAllocatortoJPH::StaticCompoundShapeSettings::Create, reducing the amount of memory allocations needed.Area3Dno longer (incorrectly) builds its compound shapes twice when added to a scene tree.Results
This is a before-and-after profiling of
SceneTree::physics_process, in a scene where aStaticBody3Dis being created every_physics_process, with 200BoxShape3Dadded to the body after it's has been added to the scene tree:For reference, here is the same scene in Godot Physics:
Note that these numbers don't include the cost you pay at the next physics step. That cost is quite small in this particular scene, with either engine, but could become more relevant as you modify more objects instead.
You may find the project here: jolt-compound-performance.zip
Footnotes
This is currently the source of some bugs, such as #75934, and in general seems quite error-prone. ↩
Note that the individual shapes themselves are still cached inbetween rebuilds. ↩
This will be the vast majority of cases for compound shapes, as this is what happens when you instantiate a scene, as opposed to programmatically creating/adding the shapes. ↩