|
@@ -157,6 +157,7 @@ void PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, int inIntegr
|
|
|
context.mStepDeltaTime = inDeltaTime / inCollisionSteps;
|
|
|
context.mSubStepDeltaTime = sub_step_delta_time;
|
|
|
context.mWarmStartImpulseRatio = warm_start_impulse_ratio;
|
|
|
+ context.mUseLargeIslandSplitter = mPhysicsSettings.mUseLargeIslandSplitter && inIntegrationSubSteps == 1; // Only use large island splitter if we don't have sub steps, not yet supported
|
|
|
context.mSteps.resize(inCollisionSteps);
|
|
|
|
|
|
// Allocate space for body pairs
|
|
@@ -379,8 +380,11 @@ void PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, int inIntegr
|
|
|
// Store the number of active bodies at the start of the step
|
|
|
next_step->mNumActiveBodiesAtStepStart = mBodyManager.GetNumActiveBodies();
|
|
|
|
|
|
- // Clear the island builder
|
|
|
+ // Clear the large island splitter
|
|
|
TempAllocator *temp_allocator = next_step->mContext->mTempAllocator;
|
|
|
+ mLargeIslandSplitter.Reset(temp_allocator);
|
|
|
+
|
|
|
+ // Clear the island builder
|
|
|
mIslandBuilder.ResetIslands(temp_allocator);
|
|
|
|
|
|
// Setup island builder
|
|
@@ -584,7 +588,10 @@ void PhysicsSystem::Update(float inDeltaTime, int inCollisionSteps, int inIntegr
|
|
|
// Validate that the cached bounds are correct
|
|
|
mBodyManager.ValidateActiveBodyBounds();
|
|
|
#endif // _DEBUG
|
|
|
-
|
|
|
+
|
|
|
+ // Clear the large island splitter
|
|
|
+ mLargeIslandSplitter.Reset(inTempAllocator);
|
|
|
+
|
|
|
// Clear the island builder
|
|
|
mIslandBuilder.ResetIslands(inTempAllocator);
|
|
|
|
|
@@ -1229,6 +1236,10 @@ void PhysicsSystem::JobFinalizeIslands(PhysicsUpdateContext *ioContext)
|
|
|
|
|
|
// Finish collecting the islands, at this point the active body list doesn't change so it's safe to access
|
|
|
mIslandBuilder.Finalize(mBodyManager.GetActiveBodiesUnsafe(), mBodyManager.GetNumActiveBodies(), mContactManager.GetNumConstraints(), ioContext->mTempAllocator);
|
|
|
+
|
|
|
+ // Prepare the large island splitter
|
|
|
+ if (ioContext->mUseLargeIslandSplitter)
|
|
|
+ mLargeIslandSplitter.Prepare(mIslandBuilder, mBodyManager.GetNumActiveBodies(), ioContext->mTempAllocator);
|
|
|
}
|
|
|
|
|
|
void PhysicsSystem::JobBodySetIslandIndex()
|
|
@@ -1264,93 +1275,157 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
|
|
|
// Only the first sub step of the first step needs to correct for the delta time difference in the previous update
|
|
|
float warm_start_impulse_ratio = ioSubStep->mIsFirstOfAll? ioContext->mWarmStartImpulseRatio : 1.0f;
|
|
|
|
|
|
- for (;;)
|
|
|
+ bool check_islands = true, check_split_islands = ioContext->mUseLargeIslandSplitter;
|
|
|
+ do
|
|
|
{
|
|
|
- // Next island
|
|
|
- uint32 island_idx = ioSubStep->mSolveVelocityConstraintsNextIsland++;
|
|
|
- if (island_idx >= mIslandBuilder.GetNumIslands())
|
|
|
- break;
|
|
|
-
|
|
|
- JPH_PROFILE("Island");
|
|
|
-
|
|
|
- // Get iterators
|
|
|
- uint32 *constraints_begin, *constraints_end;
|
|
|
- bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
|
|
|
- uint32 *contacts_begin, *contacts_end;
|
|
|
- bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
|
|
|
-
|
|
|
- if (first_sub_step)
|
|
|
+ // First try to get work from large islands
|
|
|
+ if (check_split_islands)
|
|
|
{
|
|
|
- // If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
|
|
|
- // (because they're sorted by most constraints first). This means we're done.
|
|
|
- if (!has_contacts && !has_constraints)
|
|
|
+ bool first_iteration;
|
|
|
+ uint split_island_index;
|
|
|
+ uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
|
|
|
+ switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
|
|
|
{
|
|
|
- #ifdef JPH_ENABLE_ASSERTS
|
|
|
- // Validate our assumption that the next islands don't have any constraints or contacts
|
|
|
- for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
|
|
|
+ case LargeIslandSplitter::EStatus::BatchRetrieved:
|
|
|
{
|
|
|
- JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
|
|
|
- JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
|
|
|
- }
|
|
|
- #endif // JPH_ENABLE_ASSERTS
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (first_iteration)
|
|
|
+ {
|
|
|
+ // Iteration 0 is used to warm start the batch (we added 1 to the number of iterations in LargeIslandSplitter::SplitIsland)
|
|
|
+ ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio);
|
|
|
+ mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Solve velocity constraints
|
|
|
+ ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
|
|
|
+ mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
|
|
|
+ }
|
|
|
|
|
|
- // Sort constraints to give a deterministic simulation
|
|
|
- ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
|
|
|
+ // Mark the batch as processed
|
|
|
+ bool last_iteration, final_batch;
|
|
|
+ mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
|
|
|
|
|
|
- // Sort contacts to give a deterministic simulation
|
|
|
- mContactManager.SortContacts(contacts_begin, contacts_end);
|
|
|
+ // Save back the lambdas in the contact cache for the warm start of the next physics update
|
|
|
+ if (last_sub_step && last_iteration)
|
|
|
+ mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
|
|
|
+
|
|
|
+ // We processed work, loop again
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ case LargeIslandSplitter::EStatus::WaitingForBatch:
|
|
|
+ break;
|
|
|
+ case LargeIslandSplitter::EStatus::AllBatchesDone:
|
|
|
+ check_split_islands = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ // If that didn't succeed try to process an island
|
|
|
+ if (check_islands)
|
|
|
{
|
|
|
+ // Next island
|
|
|
+ uint32 island_idx = ioSubStep->mSolveVelocityConstraintsNextIsland++;
|
|
|
+ if (island_idx >= mIslandBuilder.GetNumIslands())
|
|
|
+ {
|
|
|
+ // We processed all islands, stop checking islands
|
|
|
+ check_islands = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ JPH_PROFILE("Island");
|
|
|
+
|
|
|
+ // Get iterators for this island
|
|
|
+ uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
|
|
|
+ bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
|
|
|
+ bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
|
|
|
+
|
|
|
+ if (first_sub_step)
|
|
|
{
|
|
|
- JPH_PROFILE("Apply Gravity");
|
|
|
+ // If we don't have any contacts or constraints, we know that none of the following islands have any contacts or constraints
|
|
|
+ // (because they're sorted by most constraints first). This means we're done.
|
|
|
+ if (!has_contacts && !has_constraints)
|
|
|
+ {
|
|
|
+ #ifdef JPH_ENABLE_ASSERTS
|
|
|
+ // Validate our assumption that the next islands don't have any constraints or contacts
|
|
|
+ for (; island_idx < mIslandBuilder.GetNumIslands(); ++island_idx)
|
|
|
+ {
|
|
|
+ JPH_ASSERT(!mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end));
|
|
|
+ JPH_ASSERT(!mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end));
|
|
|
+ }
|
|
|
+ #endif // JPH_ENABLE_ASSERTS
|
|
|
|
|
|
- // Get bodies in this island
|
|
|
- BodyID *bodies_begin, *bodies_end;
|
|
|
- mIslandBuilder.GetBodiesInIsland(island_idx, bodies_begin, bodies_end);
|
|
|
+ check_islands = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // Apply gravity. In the first step this is done in a separate job.
|
|
|
- for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
+ // Sort constraints to give a deterministic simulation
|
|
|
+ ConstraintManager::sSortConstraints(active_constraints, constraints_begin, constraints_end);
|
|
|
+
|
|
|
+ // Sort contacts to give a deterministic simulation
|
|
|
+ mContactManager.SortContacts(contacts_begin, contacts_end);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
{
|
|
|
- Body &body = mBodyManager.GetBody(*body_id);
|
|
|
- if (body.IsDynamic())
|
|
|
- body.GetMotionProperties()->ApplyForceTorqueAndDragInternal(body.GetRotation(), mGravity, delta_time);
|
|
|
+ JPH_PROFILE("Apply Gravity");
|
|
|
+
|
|
|
+ // Get bodies in this island
|
|
|
+ BodyID *bodies_begin, *bodies_end;
|
|
|
+ mIslandBuilder.GetBodiesInIsland(island_idx, bodies_begin, bodies_end);
|
|
|
+
|
|
|
+ // Apply gravity. In the first step this is done in a separate job.
|
|
|
+ for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
+ {
|
|
|
+ Body &body = mBodyManager.GetBody(*body_id);
|
|
|
+ if (body.IsDynamic())
|
|
|
+ body.GetMotionProperties()->ApplyForceTorqueAndDragInternal(body.GetRotation(), mGravity, delta_time);
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ // If we don't have any contacts or constraints, we don't need to run the solver, but we do need to process
|
|
|
+ // the next island in order to apply gravity
|
|
|
+ if (!has_contacts && !has_constraints)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ // Prepare velocity constraints. In the first step this is done when adding the contact constraints.
|
|
|
+ ConstraintManager::sSetupVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
|
|
|
+ mContactManager.SetupVelocityConstraints(contacts_begin, contacts_end, delta_time);
|
|
|
}
|
|
|
|
|
|
- // If we don't have any contacts or constraints, we don't need to run the solver, but we do need to process
|
|
|
- // the next island in order to apply gravity
|
|
|
- if (!has_contacts && !has_constraints)
|
|
|
- continue;
|
|
|
+ // Split up large islands
|
|
|
+ int num_velocity_steps = mPhysicsSettings.mNumVelocitySteps;
|
|
|
+ if (ioContext->mUseLargeIslandSplitter
|
|
|
+ && mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, num_velocity_steps, mPhysicsSettings.mNumPositionSteps))
|
|
|
+ continue; // Loop again to try to fetch the newly split island
|
|
|
|
|
|
- // Prepare velocity constraints. In the first step this is done when adding the contact constraints.
|
|
|
- ConstraintManager::sSetupVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
|
|
|
- mContactManager.SetupVelocityConstraints(contacts_begin, contacts_end, delta_time);
|
|
|
- }
|
|
|
+ // We didn't create a split, just run the solver now for this entire island. Begin by warm starting.
|
|
|
+ ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, num_velocity_steps);
|
|
|
+ mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio);
|
|
|
+
|
|
|
+ // Solve velocity constraints
|
|
|
+ for (int velocity_step = 0; velocity_step < num_velocity_steps; ++velocity_step)
|
|
|
+ {
|
|
|
+ bool applied_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
|
|
|
+ applied_impulse |= mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
|
|
|
+ if (!applied_impulse)
|
|
|
+ break;
|
|
|
+ }
|
|
|
|
|
|
- // Warm start
|
|
|
- int num_velocity_steps = mPhysicsSettings.mNumVelocitySteps;
|
|
|
- ConstraintManager::sWarmStartVelocityConstraints(active_constraints, constraints_begin, constraints_end, warm_start_impulse_ratio, num_velocity_steps);
|
|
|
- mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio);
|
|
|
+ // Save back the lambdas in the contact cache for the warm start of the next physics update
|
|
|
+ if (last_sub_step)
|
|
|
+ mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
|
|
|
|
|
|
- // Solve
|
|
|
- for (int velocity_step = 0; velocity_step < num_velocity_steps; ++velocity_step)
|
|
|
- {
|
|
|
- bool constraint_impulse = ConstraintManager::sSolveVelocityConstraints(active_constraints, constraints_begin, constraints_end, delta_time);
|
|
|
- bool contact_impulse = mContactManager.SolveVelocityConstraints(contacts_begin, contacts_end);
|
|
|
- if (!constraint_impulse && !contact_impulse)
|
|
|
- break;
|
|
|
+ // We processed work, loop again
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- // Save back the lambdas in the contact cache for the warm start of the next physics update
|
|
|
- if (last_sub_step)
|
|
|
- mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
|
|
|
+ // If we didn't find any work, give up a time slice
|
|
|
+ std::this_thread::yield();
|
|
|
}
|
|
|
+ while (check_islands || check_split_islands);
|
|
|
}
|
|
|
|
|
|
-void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep) const
|
|
|
+void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
|
|
|
{
|
|
|
// Reserve enough space for all bodies that may need a cast
|
|
|
TempAllocator *temp_allocator = ioContext->mTempAllocator;
|
|
@@ -1362,6 +1437,9 @@ void PhysicsSystem::JobPreIntegrateVelocity(PhysicsUpdateContext *ioContext, Phy
|
|
|
JPH_ASSERT(ioSubStep->mActiveBodyToCCDBody == nullptr);
|
|
|
ioSubStep->mNumActiveBodyToCCDBody = mBodyManager.GetNumActiveBodies();
|
|
|
ioSubStep->mActiveBodyToCCDBody = (int *)temp_allocator->Allocate(ioSubStep->mNumActiveBodyToCCDBody * sizeof(int));
|
|
|
+
|
|
|
+ // Prepare the split island builder for solving the position constraints
|
|
|
+ mLargeIslandSplitter.PrepareForSolvePositions();
|
|
|
}
|
|
|
|
|
|
void PhysicsSystem::JobIntegrateVelocity(const PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
|
|
@@ -2060,6 +2138,107 @@ void PhysicsSystem::JobContactRemovedCallbacks(const PhysicsUpdateContext::Step
|
|
|
mContactManager.FinalizeContactCacheAndCallContactPointRemovedCallbacks(ioStep->mNumBodyPairs, ioStep->mNumManifolds);
|
|
|
}
|
|
|
|
|
|
+class PhysicsSystem::BodiesToSleep : public NonCopyable
|
|
|
+{
|
|
|
+public:
|
|
|
+ static constexpr int cBodiesToSleepSize = 512;
|
|
|
+ static constexpr int cMaxBodiesToPutInBuffer = 128;
|
|
|
+
|
|
|
+ inline BodiesToSleep(BodyManager &inBodyManager, BodyID *inBodiesToSleepBuffer) : mBodyManager(inBodyManager), mBodiesToSleepBuffer(inBodiesToSleepBuffer), mBodiesToSleepCur(inBodiesToSleepBuffer) { }
|
|
|
+
|
|
|
+ inline ~BodiesToSleep()
|
|
|
+ {
|
|
|
+ // Flush the bodies to sleep buffer
|
|
|
+ int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
|
|
|
+ if (num_bodies_in_buffer > 0)
|
|
|
+ mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ inline void PutToSleep(const BodyID *inBegin, const BodyID *inEnd)
|
|
|
+ {
|
|
|
+ int num_bodies_to_sleep = int(inEnd - inBegin);
|
|
|
+ if (num_bodies_to_sleep > cMaxBodiesToPutInBuffer)
|
|
|
+ {
|
|
|
+ // Too many bodies, deactivate immediately
|
|
|
+ mBodyManager.DeactivateBodies(inBegin, num_bodies_to_sleep);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Check if there's enough space in the bodies to sleep buffer
|
|
|
+ int num_bodies_in_buffer = int(mBodiesToSleepCur - mBodiesToSleepBuffer);
|
|
|
+ if (num_bodies_in_buffer + num_bodies_to_sleep > cBodiesToSleepSize)
|
|
|
+ {
|
|
|
+ // Flush the bodies to sleep buffer
|
|
|
+ mBodyManager.DeactivateBodies(mBodiesToSleepBuffer, num_bodies_in_buffer);
|
|
|
+ mBodiesToSleepCur = mBodiesToSleepBuffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Copy the bodies in the buffer
|
|
|
+ memcpy(mBodiesToSleepCur, inBegin, num_bodies_to_sleep * sizeof(BodyID));
|
|
|
+ mBodiesToSleepCur += num_bodies_to_sleep;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+private:
|
|
|
+ BodyManager & mBodyManager;
|
|
|
+ BodyID * mBodiesToSleepBuffer;
|
|
|
+ BodyID * mBodiesToSleepCur;
|
|
|
+};
|
|
|
+
|
|
|
+void PhysicsSystem::CheckSleepAndUpdateBounds(uint32 inIslandIndex, const PhysicsUpdateContext *ioContext, const PhysicsUpdateContext::SubStep *ioSubStep, BodiesToSleep &ioBodiesToSleep)
|
|
|
+{
|
|
|
+ // Get the bodies that belong to this island
|
|
|
+ BodyID *bodies_begin, *bodies_end;
|
|
|
+ mIslandBuilder.GetBodiesInIsland(inIslandIndex, bodies_begin, bodies_end);
|
|
|
+
|
|
|
+ // Only check sleeping in the last sub step of the last step
|
|
|
+ // Also resets force and torque used during the apply gravity phase
|
|
|
+ if (ioSubStep->mIsLastOfAll)
|
|
|
+ {
|
|
|
+ JPH_PROFILE("Check Sleeping");
|
|
|
+
|
|
|
+ static_assert(int(Body::ECanSleep::CannotSleep) == 0 && int(Body::ECanSleep::CanSleep) == 1, "Loop below makes this assumption");
|
|
|
+ int all_can_sleep = mPhysicsSettings.mAllowSleeping? int(Body::ECanSleep::CanSleep) : int(Body::ECanSleep::CannotSleep);
|
|
|
+
|
|
|
+ float time_before_sleep = mPhysicsSettings.mTimeBeforeSleep;
|
|
|
+ float max_movement = mPhysicsSettings.mPointVelocitySleepThreshold * time_before_sleep;
|
|
|
+
|
|
|
+ for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
+ {
|
|
|
+ Body &body = mBodyManager.GetBody(*body_id);
|
|
|
+
|
|
|
+ // Update bounding box
|
|
|
+ body.CalculateWorldSpaceBoundsInternal();
|
|
|
+
|
|
|
+ // Update sleeping
|
|
|
+ all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mSubStepDeltaTime, max_movement, time_before_sleep));
|
|
|
+
|
|
|
+ // Reset force and torque
|
|
|
+ body.GetMotionProperties()->ResetForceAndTorqueInternal();
|
|
|
+ }
|
|
|
+
|
|
|
+ // If all bodies indicate they can sleep we can deactivate them
|
|
|
+ if (all_can_sleep == int(Body::ECanSleep::CanSleep))
|
|
|
+ ioBodiesToSleep.PutToSleep(bodies_begin, bodies_end);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ JPH_PROFILE("Update Bounds");
|
|
|
+
|
|
|
+ // Update bounding box only for all other sub steps
|
|
|
+ for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
+ {
|
|
|
+ Body &body = mBodyManager.GetBody(*body_id);
|
|
|
+ body.CalculateWorldSpaceBoundsInternal();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so
|
|
|
+ // we need to do this every sub step)
|
|
|
+ // Note: Shuffles the BodyID's around!!!
|
|
|
+ mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);
|
|
|
+}
|
|
|
+
|
|
|
void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::SubStep *ioSubStep)
|
|
|
{
|
|
|
#ifdef JPH_ENABLE_ASSERTS
|
|
@@ -2071,141 +2250,116 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
|
|
|
#endif
|
|
|
|
|
|
float delta_time = ioContext->mSubStepDeltaTime;
|
|
|
+ float baumgarte = mPhysicsSettings.mBaumgarte;
|
|
|
Constraint **active_constraints = ioContext->mActiveConstraints;
|
|
|
|
|
|
// Keep a buffer of bodies that need to go to sleep in order to not constantly lock the active bodies mutex and create contention between all solving threads
|
|
|
- constexpr int cBodiesToSleepSize = 512;
|
|
|
- constexpr int cMaxBodiesToPutInBuffer = 64;
|
|
|
- BodyID *bodies_to_sleep = (BodyID *)JPH_STACK_ALLOC(cBodiesToSleepSize * sizeof(BodyID));
|
|
|
- BodyID *bodies_to_sleep_cur = bodies_to_sleep;
|
|
|
+ BodiesToSleep bodies_to_sleep(mBodyManager, (BodyID *)JPH_STACK_ALLOC(BodiesToSleep::cBodiesToSleepSize * sizeof(BodyID)));
|
|
|
|
|
|
- for (;;)
|
|
|
+ bool check_islands = true, check_split_islands = ioContext->mUseLargeIslandSplitter;
|
|
|
+ do
|
|
|
{
|
|
|
- // Next island
|
|
|
- uint32 island_idx = ioSubStep->mSolvePositionConstraintsNextIsland++;
|
|
|
- if (island_idx >= mIslandBuilder.GetNumIslands())
|
|
|
- break;
|
|
|
-
|
|
|
- JPH_PROFILE("Island");
|
|
|
-
|
|
|
- // Get iterators for this island
|
|
|
- BodyID *bodies_begin, *bodies_end;
|
|
|
- mIslandBuilder.GetBodiesInIsland(island_idx, bodies_begin, bodies_end);
|
|
|
- uint32 *constraints_begin, *constraints_end;
|
|
|
- bool has_constraints = mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
|
|
|
- uint32 *contacts_begin, *contacts_end;
|
|
|
- bool has_contacts = mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
|
|
|
-
|
|
|
- // Correct positions
|
|
|
- if (has_contacts || has_constraints)
|
|
|
+ // First try to get work from large islands
|
|
|
+ if (check_split_islands)
|
|
|
{
|
|
|
- float baumgarte = mPhysicsSettings.mBaumgarte;
|
|
|
-
|
|
|
- // First iteration
|
|
|
- int num_position_steps = mPhysicsSettings.mNumPositionSteps;
|
|
|
- if (num_position_steps > 0)
|
|
|
+ bool first_iteration;
|
|
|
+ uint split_island_index;
|
|
|
+ uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
|
|
|
+ switch (mLargeIslandSplitter.FetchNextBatch(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, first_iteration))
|
|
|
{
|
|
|
- // In the first iteration also calculate the number of position steps (this way we avoid pulling all constraints into the cache twice)
|
|
|
- bool constraint_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte, num_position_steps);
|
|
|
- bool contact_impulse = mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
|
|
|
+ case LargeIslandSplitter::EStatus::BatchRetrieved:
|
|
|
+ // Solve the batch
|
|
|
+ ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
|
|
|
+ mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
|
|
|
|
|
|
- // If no impulses were applied we can stop, otherwise we already did 1 iteration
|
|
|
- if (!constraint_impulse && !contact_impulse)
|
|
|
- num_position_steps = 0;
|
|
|
- else
|
|
|
- --num_position_steps;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Iterate the constraints to see if they override the amount of position steps
|
|
|
- for (const uint32 *c = constraints_begin; c < constraints_end; ++c)
|
|
|
- num_position_steps = max(num_position_steps, active_constraints[*c]->GetNumPositionStepsOverride());
|
|
|
- }
|
|
|
+ // Mark the batch as processed
|
|
|
+ bool last_iteration, final_batch;
|
|
|
+ mLargeIslandSplitter.MarkBatchProcessed(split_island_index, constraints_begin, constraints_end, contacts_begin, contacts_end, last_iteration, final_batch);
|
|
|
|
|
|
- // Further iterations
|
|
|
- for (int position_step = 0; position_step < num_position_steps; ++position_step)
|
|
|
- {
|
|
|
- bool constraint_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
|
|
|
- bool contact_impulse = mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
|
|
|
- if (!constraint_impulse && !contact_impulse)
|
|
|
- break;
|
|
|
+ // The final batch will update all bounds and check sleeping
|
|
|
+ if (final_batch)
|
|
|
+ CheckSleepAndUpdateBounds(mLargeIslandSplitter.GetIslandIndex(split_island_index), ioContext, ioSubStep, bodies_to_sleep);
|
|
|
+
|
|
|
+ // We processed work, loop again
|
|
|
+ continue;
|
|
|
+ case LargeIslandSplitter::EStatus::WaitingForBatch:
|
|
|
+ break;
|
|
|
+ case LargeIslandSplitter::EStatus::AllBatchesDone:
|
|
|
+ check_split_islands = false;
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Only check sleeping in the last sub step of the last step
|
|
|
- // Also resets force and torque used during the apply gravity phase
|
|
|
- if (ioSubStep->mIsLastOfAll)
|
|
|
+ // If that didn't succeed try to process an island
|
|
|
+ if (check_islands)
|
|
|
{
|
|
|
- JPH_PROFILE("Check Sleeping");
|
|
|
-
|
|
|
- static_assert(int(Body::ECanSleep::CannotSleep) == 0 && int(Body::ECanSleep::CanSleep) == 1, "Loop below makes this assumption");
|
|
|
- int all_can_sleep = mPhysicsSettings.mAllowSleeping? int(Body::ECanSleep::CanSleep) : int(Body::ECanSleep::CannotSleep);
|
|
|
-
|
|
|
- float time_before_sleep = mPhysicsSettings.mTimeBeforeSleep;
|
|
|
- float max_movement = mPhysicsSettings.mPointVelocitySleepThreshold * time_before_sleep;
|
|
|
-
|
|
|
- for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
+ // Next island
|
|
|
+ uint32 island_idx = ioSubStep->mSolvePositionConstraintsNextIsland++;
|
|
|
+ if (island_idx >= mIslandBuilder.GetNumIslands())
|
|
|
{
|
|
|
- Body &body = mBodyManager.GetBody(*body_id);
|
|
|
+ // We processed all islands, stop checking islands
|
|
|
+ check_islands = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- // Update bounding box
|
|
|
- body.CalculateWorldSpaceBoundsInternal();
|
|
|
+ JPH_PROFILE("Island");
|
|
|
|
|
|
- // Update sleeping
|
|
|
- all_can_sleep &= int(body.UpdateSleepStateInternal(ioContext->mSubStepDeltaTime, max_movement, time_before_sleep));
|
|
|
+ // Get iterators for this island
|
|
|
+ uint32 *constraints_begin, *constraints_end, *contacts_begin, *contacts_end;
|
|
|
+ mIslandBuilder.GetConstraintsInIsland(island_idx, constraints_begin, constraints_end);
|
|
|
+ mIslandBuilder.GetContactsInIsland(island_idx, contacts_begin, contacts_end);
|
|
|
|
|
|
- // Reset force and torque
|
|
|
- body.GetMotionProperties()->ResetForceAndTorqueInternal();
|
|
|
- }
|
|
|
+ // If this island is a large island, it will be picked up as a batch and we don't need to do anything here
|
|
|
+ uint num_items = uint(constraints_end - constraints_begin) + uint(contacts_end - contacts_begin);
|
|
|
+ if (ioContext->mUseLargeIslandSplitter
|
|
|
+ && num_items >= LargeIslandSplitter::cLargeIslandTreshold)
|
|
|
+ continue;
|
|
|
|
|
|
- // If all bodies indicate they can sleep we can deactivate them
|
|
|
- if (all_can_sleep == int(Body::ECanSleep::CanSleep))
|
|
|
+ // Check if this island needs solving
|
|
|
+ if (num_items > 0)
|
|
|
{
|
|
|
- int num_bodies_to_sleep = int(bodies_end - bodies_begin);
|
|
|
- if (num_bodies_to_sleep > cMaxBodiesToPutInBuffer)
|
|
|
+ // First iteration
|
|
|
+ int num_position_steps = mPhysicsSettings.mNumPositionSteps;
|
|
|
+ if (num_position_steps > 0)
|
|
|
{
|
|
|
- // Too many bodies, deactivate immediately
|
|
|
- mBodyManager.DeactivateBodies(bodies_begin, num_bodies_to_sleep);
|
|
|
+ // In the first iteration also calculate the number of position steps (this way we avoid pulling all constraints into the cache twice)
|
|
|
+ bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte, num_position_steps);
|
|
|
+ applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
|
|
|
+
|
|
|
+ // If no impulses were applied we can stop, otherwise we already did 1 iteration
|
|
|
+ if (!applied_impulse)
|
|
|
+ num_position_steps = 0;
|
|
|
+ else
|
|
|
+ --num_position_steps;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- // Check if there's enough space in the bodies to sleep buffer
|
|
|
- int num_bodies_in_buffer = int(bodies_to_sleep_cur - bodies_to_sleep);
|
|
|
- if (num_bodies_in_buffer + num_bodies_to_sleep > cBodiesToSleepSize)
|
|
|
- {
|
|
|
- // Flush the bodies to sleep buffer
|
|
|
- mBodyManager.DeactivateBodies(bodies_to_sleep, num_bodies_in_buffer);
|
|
|
- bodies_to_sleep_cur = bodies_to_sleep;
|
|
|
- }
|
|
|
+ // Iterate the constraints to see if they override the number of position steps
|
|
|
+ for (const uint32 *c = constraints_begin; c < constraints_end; ++c)
|
|
|
+ num_position_steps = max(num_position_steps, active_constraints[*c]->GetNumPositionStepsOverride());
|
|
|
+ }
|
|
|
|
|
|
- // Copy the bodies in the buffer
|
|
|
- memcpy(bodies_to_sleep_cur, bodies_begin, num_bodies_to_sleep * sizeof(BodyID));
|
|
|
- bodies_to_sleep_cur += num_bodies_to_sleep;
|
|
|
+ // Further iterations
|
|
|
+ for (int position_step = 0; position_step < num_position_steps; ++position_step)
|
|
|
+ {
|
|
|
+ bool applied_impulse = ConstraintManager::sSolvePositionConstraints(active_constraints, constraints_begin, constraints_end, delta_time, baumgarte);
|
|
|
+ applied_impulse |= mContactManager.SolvePositionConstraints(contacts_begin, contacts_end);
|
|
|
+ if (!applied_impulse)
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- JPH_PROFILE("Update Bounds");
|
|
|
|
|
|
- // Update bounding box only for all other sub steps
|
|
|
- for (const BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
|
|
|
- {
|
|
|
- Body &body = mBodyManager.GetBody(*body_id);
|
|
|
- body.CalculateWorldSpaceBoundsInternal();
|
|
|
- }
|
|
|
+ // After solving we will update all bounds and check sleeping
|
|
|
+ CheckSleepAndUpdateBounds(island_idx, ioContext, ioSubStep, bodies_to_sleep);
|
|
|
+
|
|
|
+ // We processed work, loop again
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- // Notify broadphase of changed objects (find ccd contacts can do linear casts in the next step, so
|
|
|
- // we need to do this every sub step)
|
|
|
- // Note: Shuffles the BodyID's around!!!
|
|
|
- mBroadPhase->NotifyBodiesAABBChanged(bodies_begin, int(bodies_end - bodies_begin), false);
|
|
|
+ // If we didn't find any work, give up a time slice
|
|
|
+ std::this_thread::yield();
|
|
|
}
|
|
|
-
|
|
|
- // Flush the bodies to sleep buffer
|
|
|
- int num_bodies_in_buffer = int(bodies_to_sleep_cur - bodies_to_sleep);
|
|
|
- if (num_bodies_in_buffer > 0)
|
|
|
- mBodyManager.DeactivateBodies(bodies_to_sleep, num_bodies_in_buffer);
|
|
|
+ while (check_islands || check_split_islands);
|
|
|
}
|
|
|
|
|
|
void PhysicsSystem::SaveState(StateRecorder &inStream) const
|