Browse Source

Extended simulation stats (#1776)

- Added time spent in the broad phase
- If part of large island
- Separate solve position ticks from update bounds ticks
- Only dynamic objects get solve velocity/position ticks assigned now
Jorrit Rouwe 1 week ago
parent
commit
2021f6aa05

+ 5 - 2
Jolt/Physics/Body/BodyManager.cpp

@@ -1189,7 +1189,7 @@ void BodyManager::ReportSimulationStats()
 {
 	UniqueLock lock(mActiveBodiesMutex JPH_IF_ENABLE_ASSERTS(, this, EPhysicsLockTypes::ActiveBodiesList));
 
-	Trace("BodyID, IslandIndex, NarrowPhase (us), VelocityConstraint (us), PositionConstraint (us), CCD (us), NumContactConstraints, NumVelocitySteps, NumPositionSteps");
+	Trace("BodyID, IslandIndex, LargeIsland, BroadPhase (us), NarrowPhase (us), VelocityConstraint (us), PositionConstraint (us), UpdateBounds (us), CCD (us), NumContactConstraints, NumVelocitySteps, NumPositionSteps");
 
 	double us_per_tick = 1000000.0 / Profiler::sInstance->GetProcessorTicksPerSecond();
 
@@ -1199,12 +1199,15 @@ void BodyManager::ReportSimulationStats()
 			const Body *body = mBodies[id->GetIndex()];
 			const MotionProperties *mp = body->mMotionProperties;
 			const MotionProperties::SimulationStats &stats = mp->GetSimulationStats();
-			Trace("%u, %u, %.2f, %.2f, %.2f, %.2f, %u, %u, %u",
+			Trace("%u, %u, %s, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %u, %u, %u",
 				body->GetID().GetIndex(),
 				mp->GetIslandIndexInternal(),
+				stats.mIsLargeIsland? "True" : "False",
+				double(stats.mBroadPhaseTicks) * us_per_tick,
 				double(stats.mNarrowPhaseTicks) * us_per_tick,
 				double(stats.mVelocityConstraintTicks) * us_per_tick,
 				double(stats.mPositionConstraintTicks) * us_per_tick,
+				double(stats.mUpdateBoundsTicks) * us_per_tick,
 				double(stats.mCCDTicks) * us_per_tick,
 				stats.mNumContactConstraints.load(memory_order_relaxed),
 				stats.mNumVelocitySteps,

+ 5 - 2
Jolt/Physics/Body/MotionProperties.h

@@ -190,15 +190,18 @@ public:
 	/// Stats for this body. These are average for the simulation island the body was part of.
 	struct SimulationStats
 	{
-		void				Reset()															{ mNarrowPhaseTicks.store(0, memory_order_relaxed); mVelocityConstraintTicks = 0; mPositionConstraintTicks = 0; mCCDTicks.store(0, memory_order_relaxed); mNumContactConstraints.store(0, memory_order_relaxed); mNumVelocitySteps = 0; mNumPositionSteps = 0; }
+		void				Reset()															{ mBroadPhaseTicks = 0; mNarrowPhaseTicks.store(0, memory_order_relaxed); mVelocityConstraintTicks = 0; mPositionConstraintTicks = 0; mUpdateBoundsTicks = 0; mCCDTicks.store(0, memory_order_relaxed); mNumContactConstraints.store(0, memory_order_relaxed); mNumVelocitySteps = 0; mNumPositionSteps = 0; mIsLargeIsland = false; }
 
-		atomic<uint64>		mNarrowPhaseTicks = 0;											///< Number of processor ticks spent doing narrow phase collision detection
+		uint64				mBroadPhaseTicks = 0;											///< Number of processor ticks spent doing broad phase collision detection
+		atomic<uint64>		mNarrowPhaseTicks = 0;											///< Number of ticks spent doing narrow phase collision detection
 		uint64				mVelocityConstraintTicks = 0;									///< Number of ticks spent solving velocity constraints
 		uint64				mPositionConstraintTicks = 0;									///< Number of ticks spent solving position constraints
+		uint64				mUpdateBoundsTicks = 0;											///< Number of ticks spent updating the broadphase and checking if the body should go to sleep
 		atomic<uint64>		mCCDTicks = 0;													///< Number of ticks spent doing CCD
 		atomic<uint32>		mNumContactConstraints = 0;										///< Number of contact constraints created for this body
 		uint8				mNumVelocitySteps = 0;											///< Number of velocity iterations performed
 		uint8				mNumPositionSteps = 0;											///< Number of position iterations performed
+		bool				mIsLargeIsland = false;											///< If this body was part of a large island
 	};
 
 	const SimulationStats &	GetSimulationStats() const										{ return mSimulationStats; }

+ 9 - 0
Jolt/Physics/Collision/BroadPhase/QuadTree.cpp

@@ -1452,6 +1452,10 @@ void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inAc
 		const Body &body1 = *inBodies[b1_id.GetIndex()];
 		JPH_ASSERT(!body1.IsStatic());
 
+	#ifdef JPH_TRACK_SIMULATION_STATS
+		uint64 start_tick = GetProcessorTickCount();
+	#endif
+
 		// Expand the bounding box by the speculative contact distance
 		AABox bounds1 = body1.GetWorldSpaceBounds();
 		bounds1.ExpandBy(Vec3::sReplicate(inSpeculativeContactDistance));
@@ -1521,6 +1525,11 @@ void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inAc
 			--top;
 		}
 		while (top >= 0);
+
+	#ifdef JPH_TRACK_SIMULATION_STATS
+		uint64 num_ticks = GetProcessorTickCount() - start_tick;
+		const_cast<MotionProperties::SimulationStats &>(body1.GetMotionPropertiesUnchecked()->GetSimulationStats()).mBroadPhaseTicks += num_ticks;
+	#endif
 	}
 
 	// Test that the root node was not swapped while finding collision pairs.

+ 3 - 0
Jolt/Physics/IslandBuilder.h

@@ -59,7 +59,10 @@ public:
 	{
 		atomic<uint64>		mVelocityConstraintTicks = 0;
 		atomic<uint64>		mPositionConstraintTicks = 0;
+		atomic<uint64>		mUpdateBoundsTicks = 0;
 		uint8				mNumVelocitySteps = 0;
+		uint8				mNumPositionSteps = 0;												///< Tracking this a 2nd time since IslandBuilder::mNumPositionSteps is not filled in when there are no constraints or for large islands.
+		bool				mIsLargeIsland = false;
 	};
 
 	/// Tracks simulation stats per island

+ 43 - 15
Jolt/Physics/PhysicsSystem.cpp

@@ -140,19 +140,32 @@ void PhysicsSystem::GatherIslandStats()
 		mIslandBuilder.GetBodiesInIsland(island_idx, bodies_begin, bodies_end);
 		uint64 num_bodies = bodies_end - bodies_begin;
 
+		// Calculate the number of dynamic bodies
+		uint64 num_dynamic_bodies = 0;
+		for (BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
+			if (mBodyManager.GetBody(*body_id).GetMotionType() == EMotionType::Dynamic)
+				++num_dynamic_bodies;
+		num_dynamic_bodies = max<uint64>(num_dynamic_bodies, 1); // Ensure we don't divide by zero
+
 		// Equally distribute the stats over all bodies
 		const IslandBuilder::IslandStats &stats = mIslandBuilder.GetIslandStats(island_idx);
-		uint64 num_velocity_ticks = stats.mVelocityConstraintTicks / num_bodies;
-		uint64 num_position_ticks = stats.mPositionConstraintTicks / num_bodies;
-		uint8 num_position_steps = (uint8)mIslandBuilder.GetNumPositionSteps(island_idx);
+		uint64 num_velocity_ticks = stats.mVelocityConstraintTicks / num_dynamic_bodies;
+		uint64 num_position_ticks = stats.mPositionConstraintTicks / num_dynamic_bodies;
+		uint64 num_update_bounds_ticks = stats.mUpdateBoundsTicks / num_bodies;
 
 		for (BodyID *body_id = bodies_begin; body_id < bodies_end; ++body_id)
 		{
-			MotionProperties::SimulationStats &out_stats = mBodyManager.GetBody(*body_id).GetMotionProperties()->GetSimulationStats();
+			Body &body = mBodyManager.GetBody(*body_id);
+			MotionProperties::SimulationStats &out_stats = body.GetMotionProperties()->GetSimulationStats();
 			out_stats.mNumVelocitySteps = stats.mNumVelocitySteps;
-			out_stats.mNumPositionSteps = num_position_steps;
-			out_stats.mVelocityConstraintTicks += num_velocity_ticks; // In case of multiple collision steps we accumulate
-			out_stats.mPositionConstraintTicks += num_position_ticks;
+			out_stats.mNumPositionSteps = stats.mNumPositionSteps;
+			if (body.GetMotionType() == EMotionType::Dynamic)
+			{
+				out_stats.mVelocityConstraintTicks += num_velocity_ticks; // In case of multiple collision steps we accumulate
+				out_stats.mPositionConstraintTicks += num_position_ticks;
+			}
+			out_stats.mUpdateBoundsTicks += num_update_bounds_ticks;
+			out_stats.mIsLargeIsland = stats.mIsLargeIsland;
 		}
 	}
 }
@@ -1504,15 +1517,25 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 			}
 
 			// Split up large islands
+		#ifdef JPH_TRACK_SIMULATION_STATS
+			bool is_large_island = true;
+		#endif
 			CalculateSolverSteps steps_calculator(mPhysicsSettings);
 			if (!mPhysicsSettings.mUseLargeIslandSplitter
 				|| !mLargeIslandSplitter.SplitIsland(island_idx, mIslandBuilder, mBodyManager, mContactManager, active_constraints, steps_calculator))
 			{
+			#ifdef JPH_TRACK_SIMULATION_STATS
+				is_large_island = false;
+			#endif
+
 				// 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, steps_calculator);
 				mContactManager.WarmStartVelocityConstraints(contacts_begin, contacts_end, warm_start_impulse_ratio, steps_calculator);
 				steps_calculator.Finalize();
 
+				// Store the number of position steps for later
+				mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps());
+
 				// Solve velocity constraints
 				for (uint velocity_step = 0; velocity_step < steps_calculator.GetNumVelocitySteps(); ++velocity_step)
 				{
@@ -1526,14 +1549,13 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 				mContactManager.StoreAppliedImpulses(contacts_begin, contacts_end);
 			}
 
-			// Store the number of position steps for later
-			mIslandBuilder.SetNumPositionSteps(island_idx, steps_calculator.GetNumPositionSteps());
-
 		#ifdef JPH_TRACK_SIMULATION_STATS
 			uint64 num_ticks = GetProcessorTickCount() - start_tick;
 			IslandBuilder::IslandStats &stats = mIslandBuilder.GetIslandStats(island_idx);
 			stats.mNumVelocitySteps = (uint8)steps_calculator.GetNumVelocitySteps();
+			stats.mNumPositionSteps = (uint8)steps_calculator.GetNumPositionSteps();
 			stats.mVelocityConstraintTicks.fetch_add(num_ticks, memory_order_relaxed);
+			stats.mIsLargeIsland = is_large_island;
 		#endif
 
 			// We processed work, loop again
@@ -1802,7 +1824,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 		if (idx >= ioStep->mNumCCDBodies)
 			break;
 		CCDBody &ccd_body = ioStep->mCCDBodies[idx];
-		Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);
+		const Body &body = mBodyManager.GetBody(ccd_body.mBodyID1);
 
 		// Filter out layers
 		DefaultBroadPhaseLayerFilter broadphase_layer_filter = GetDefaultBroadPhaseLayerFilter(body.GetObjectLayer());
@@ -2030,7 +2052,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 
 	#ifdef JPH_TRACK_SIMULATION_STATS
 		uint64 num_ticks = GetProcessorTickCount() - start_tick;
-		body.GetMotionPropertiesUnchecked()->GetSimulationStats().mCCDTicks.fetch_add(num_ticks, memory_order_relaxed);
+		const_cast<MotionProperties::SimulationStats &>(body.GetMotionPropertiesUnchecked()->GetSimulationStats()).mCCDTicks.fetch_add(num_ticks, memory_order_relaxed);
 	#endif
 
 		// Check if there was a hit
@@ -2575,13 +2597,19 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 				}
 			}
 
+		#ifdef JPH_TRACK_SIMULATION_STATS
+			// Accumulate time spent in solving position constraints
+			uint64 solve_position_ticks = GetProcessorTickCount();
+			IslandBuilder::IslandStats &stats = mIslandBuilder.GetIslandStats(island_idx);
+			stats.mPositionConstraintTicks.fetch_add(solve_position_ticks - start_tick, memory_order_relaxed);
+		#endif
+
 			// After solving we will update all bounds and check sleeping
 			CheckSleepAndUpdateBounds(island_idx, ioContext, ioStep, bodies_to_sleep);
 
 		#ifdef JPH_TRACK_SIMULATION_STATS
-			// Average the total ticks spent over the number of bodies and assign each the average number of ticks per body
-			uint64 num_ticks = GetProcessorTickCount() - start_tick;
-			mIslandBuilder.GetIslandStats(island_idx).mPositionConstraintTicks.fetch_add(num_ticks, memory_order_relaxed);
+			// Accumulate time spent in updating bounding box
+			stats.mUpdateBoundsTicks.fetch_add(GetProcessorTickCount() - solve_position_ticks, memory_order_relaxed);
 		#endif
 
 			// We processed work, loop again