-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Optimized physics object spawn time #39726
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,7 +79,7 @@ btTransform CollisionObjectBullet::ShapeWrapper::get_adjusted_transform() const | |
} | ||
|
||
void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_scale) { | ||
if (!bt_shape) { | ||
if (bt_shape == nullptr) { | ||
if (active) { | ||
bt_shape = shape->create_bt_shape(scale * body_scale); | ||
} else { | ||
|
@@ -88,6 +88,13 @@ void CollisionObjectBullet::ShapeWrapper::claim_bt_shape(const btVector3 &body_s | |
} | ||
} | ||
|
||
void CollisionObjectBullet::ShapeWrapper::release_bt_shape() { | ||
if (bt_shape != nullptr) { | ||
shape->destroy_bt_shape(bt_shape); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I prefer |
||
bt_shape = nullptr; | ||
} | ||
} | ||
|
||
CollisionObjectBullet::CollisionObjectBullet(Type p_type) : | ||
RIDBullet(), | ||
type(p_type) {} | ||
|
@@ -158,6 +165,13 @@ bool CollisionObjectBullet::has_collision_exception(const CollisionObjectBullet | |
return !bt_collision_object->checkCollideWith(p_otherCollisionObject->bt_collision_object); | ||
} | ||
|
||
void CollisionObjectBullet::prepare_object_for_dispatch() { | ||
if (need_body_reload) { | ||
do_reload_body(); | ||
need_body_reload = false; | ||
} | ||
} | ||
|
||
void CollisionObjectBullet::set_collision_enabled(bool p_enabled) { | ||
collisionsEnabled = p_enabled; | ||
if (collisionsEnabled) { | ||
|
@@ -319,58 +333,68 @@ bool RigidCollisionObjectBullet::is_shape_disabled(int p_index) { | |
return !shapes[p_index].active; | ||
} | ||
|
||
void RigidCollisionObjectBullet::prepare_object_for_dispatch() { | ||
if (need_shape_reload) { | ||
do_reload_shapes(); | ||
need_shape_reload = false; | ||
} | ||
CollisionObjectBullet::prepare_object_for_dispatch(); | ||
} | ||
|
||
void RigidCollisionObjectBullet::shape_changed(int p_shape_index) { | ||
ShapeWrapper &shp = shapes.write[p_shape_index]; | ||
if (shp.bt_shape == mainShape) { | ||
mainShape = nullptr; | ||
} | ||
bulletdelete(shp.bt_shape); | ||
shp.release_bt_shape(); | ||
reload_shapes(); | ||
} | ||
|
||
void RigidCollisionObjectBullet::reload_shapes() { | ||
need_shape_reload = true; | ||
} | ||
|
||
void RigidCollisionObjectBullet::do_reload_shapes() { | ||
if (mainShape && mainShape->isCompound()) { | ||
// Destroy compound | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Postponing the reloading of shapes until later causes crashes if those shapes are needed before
Since each of these changes could be made before needing the shapes in a single user Furthermore, the test used to demonstrate the effectiveness of this PR is misleading, because it doesn't include the time for subsequently calling Test project: 39726.zip Using this test project with 1,000 Godot Physics: Bullet Physics: In other words, not only does this PR cause numerous crashes that may never be resolvable, it doesn't achieve the performance gain it set out to achieve; the slow Bullet Frame Rate is still there. Therefore, I think this PR should be reverted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the code used for the test:
To be fair, I think that your test doesn't prove what you claim. It adds 1000 However, is important to say that this PR want to optimize the performance relative to Bullet itself and not relative to GodotPhysics which was running already well. I don't think that the introduced bugs are a good reason to revert the optimization done instead of fixing the issues. It's like say that the Vulkan implementation is bad because is half broken, or the GDScript v2 is bad because it's not yet fully stable: and so both should be reverted. The mentioned bug, especially #40840, have a solution: #40886 (comment) please let me know if you want to do that change, if no I can provide a new PR to fix that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not claiming anything. I'm refuting the claims made to support this PR; specifically that it solves #30027. I'm also highlighting why it has introduced numerous crashing issues, and suggesting that a PR that causes more problems than it solves should be reverted. My test, which is a modified version of the original test provided to support this PR, shows that only looking at the time passed at the start of the first call to However, more importantly, this PR does not solve issue #30027. The PR reduces the number of times that the function that reloads the shapes is called, but these calls are not the source of the performance problem. The primary source of the performance problem is in the While the actual gains of this PR are minimal, the issues caused by this change are significant. The more I investigate the crashing issues caused, the more I'm convinced they will not be solved with multiple band-aid fixes like #40886 (comment) without ultimately reverting this change, or worse: actually increasing the number of calls to In summary, I believe this PR should be reverted, because it causes multiple crashing issues while doing little to improve actual performance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check this: #41082 (comment) |
||
bulletdelete(mainShape); | ||
} | ||
|
||
mainShape = nullptr; | ||
|
||
ShapeWrapper *shpWrapper; | ||
const int shape_count = shapes.size(); | ||
ShapeWrapper *shapes_ptr = shapes.ptrw(); | ||
|
||
// Reset shape if required | ||
// Reset all shapes if required | ||
if (force_shape_reset) { | ||
for (int i(0); i < shape_count; ++i) { | ||
shpWrapper = &shapes.write[i]; | ||
bulletdelete(shpWrapper->bt_shape); | ||
shapes_ptr[i].release_bt_shape(); | ||
} | ||
force_shape_reset = false; | ||
} | ||
|
||
const btVector3 body_scale(get_bt_body_scale()); | ||
|
||
// Try to optimize by not using compound | ||
if (1 == shape_count) { | ||
shpWrapper = &shapes.write[0]; | ||
btTransform transform = shpWrapper->get_adjusted_transform(); | ||
// Is it possible to optimize by not using compound? | ||
btTransform transform = shapes_ptr[0].get_adjusted_transform(); | ||
if (transform.getOrigin().isZero() && transform.getBasis() == transform.getBasis().getIdentity()) { | ||
shpWrapper->claim_bt_shape(body_scale); | ||
mainShape = shpWrapper->bt_shape; | ||
shapes_ptr[0].claim_bt_shape(body_scale); | ||
mainShape = shapes_ptr[0].bt_shape; | ||
main_shape_changed(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the pointer of an array, and just above is used as so. I'm not using the dereferencing syntax to keep the code coherent and more clear. |
||
// Nothing more to do | ||
return; | ||
} | ||
} | ||
|
||
// Optimization not possible use a compound shape | ||
// Optimization not possible use a compound shape. | ||
btCompoundShape *compoundShape = bulletnew(btCompoundShape(enableDynamicAabbTree, shape_count)); | ||
|
||
for (int i(0); i < shape_count; ++i) { | ||
shpWrapper = &shapes.write[i]; | ||
shpWrapper->claim_bt_shape(body_scale); | ||
btTransform scaled_shape_transform(shpWrapper->get_adjusted_transform()); | ||
shapes_ptr[i].claim_bt_shape(body_scale); | ||
btTransform scaled_shape_transform(shapes_ptr[i].get_adjusted_transform()); | ||
scaled_shape_transform.getOrigin() *= body_scale; | ||
compoundShape->addChildShape(scaled_shape_transform, shpWrapper->bt_shape); | ||
compoundShape->addChildShape(scaled_shape_transform, shapes_ptr[i].bt_shape); | ||
} | ||
|
||
compoundShape->recalculateLocalAabb(); | ||
|
@@ -389,5 +413,5 @@ void RigidCollisionObjectBullet::internal_shape_destroy(int p_index, bool p_perm | |
if (shp.bt_shape == mainShape) { | ||
mainShape = nullptr; | ||
} | ||
bulletdelete(shp.bt_shape); | ||
shp.release_bt_shape(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,11 +70,12 @@ class CollisionObjectBullet : public RIDBullet { | |
|
||
struct ShapeWrapper { | ||
ShapeBullet *shape = nullptr; | ||
btCollisionShape *bt_shape = nullptr; | ||
btTransform transform; | ||
btVector3 scale; | ||
bool active = true; | ||
btCollisionShape *bt_shape = nullptr; | ||
|
||
public: | ||
ShapeWrapper() {} | ||
|
||
ShapeWrapper(ShapeBullet *p_shape, const btTransform &p_transform, bool p_active) : | ||
|
@@ -107,6 +108,7 @@ class CollisionObjectBullet : public RIDBullet { | |
btTransform get_adjusted_transform() const; | ||
|
||
void claim_bt_shape(const btVector3 &body_scale); | ||
void release_bt_shape(); | ||
}; | ||
|
||
protected: | ||
|
@@ -124,12 +126,17 @@ class CollisionObjectBullet : public RIDBullet { | |
|
||
VSet<RID> exceptions; | ||
|
||
bool need_body_reload = true; | ||
|
||
/// This array is used to know all areas where this Object is overlapped in | ||
/// New area is added when overlap with new area (AreaBullet::addOverlap), then is removed when it exit (CollisionObjectBullet::onExitArea) | ||
/// This array is used mainly to know which area hold the pointer of this object | ||
Vector<AreaBullet *> areasOverlapped; | ||
bool isTransformChanged = false; | ||
|
||
public: | ||
bool is_in_world = false; | ||
|
||
public: | ||
CollisionObjectBullet(Type p_type); | ||
virtual ~CollisionObjectBullet(); | ||
|
@@ -183,13 +190,21 @@ class CollisionObjectBullet : public RIDBullet { | |
return collisionLayer & p_other->collisionMask || p_other->collisionLayer & collisionMask; | ||
} | ||
|
||
virtual void reload_body() = 0; | ||
bool need_reload_body() const { | ||
return need_body_reload; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but it's the getter of a variable, it's there for code completeness. |
||
void reload_body() { | ||
need_body_reload = true; | ||
} | ||
virtual void do_reload_body() = 0; | ||
virtual void set_space(SpaceBullet *p_space) = 0; | ||
_FORCE_INLINE_ SpaceBullet *get_space() const { return space; } | ||
|
||
virtual void on_collision_checker_start() = 0; | ||
virtual void on_collision_checker_end() = 0; | ||
|
||
virtual void prepare_object_for_dispatch(); | ||
virtual void dispatch_callbacks() = 0; | ||
|
||
void set_collision_enabled(bool p_enabled); | ||
|
@@ -215,6 +230,7 @@ class RigidCollisionObjectBullet : public CollisionObjectBullet, public ShapeOwn | |
protected: | ||
btCollisionShape *mainShape = nullptr; | ||
Vector<ShapeWrapper> shapes; | ||
bool need_shape_reload = true; | ||
|
||
public: | ||
RigidCollisionObjectBullet(Type p_type) : | ||
|
@@ -246,8 +262,12 @@ class RigidCollisionObjectBullet : public CollisionObjectBullet, public ShapeOwn | |
void set_shape_disabled(int p_index, bool p_disabled); | ||
bool is_shape_disabled(int p_index); | ||
|
||
virtual void prepare_object_for_dispatch(); | ||
|
||
virtual void shape_changed(int p_shape_index); | ||
virtual void reload_shapes(); | ||
void reload_shapes(); | ||
bool need_reload_shapes() const { return need_shape_reload; } | ||
virtual void do_reload_shapes(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far a I can tell, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but it's the getter of a variable, it's there for code completeness. |
||
|
||
virtual void main_shape_changed() = 0; | ||
virtual void body_scale_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.
A matter of taste, but I prefer the original
if (!bt_shape)
, or better yet, the opposite:if (bt_shape) return;
to avoid needing to indent the code.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.
The result is the same, but in this way it's much more clear that it's a pointer. The indentation just make the code more clear, in this case, for this reason I did it so.