Browse Source

Fixed running out of stack space when simulating a really high number of active rigid bodies (#1501)

* Added performance test to simulate max allowed number of bodies
* Fix int overflow issue in DX12 instance rendering implementation
Jorrit Rouwe 6 months ago
parent
commit
63c5020e2e

+ 1 - 0
Docs/ReleaseNotes.md

@@ -44,6 +44,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * An empty `MutableCompoundShape` now returns the same local bounding box as `EmptyShape` (a point at (0, 0, 0)). This prevents floating point overflow exceptions.
 * An empty `MutableCompoundShape` now returns the same local bounding box as `EmptyShape` (a point at (0, 0, 0)). This prevents floating point overflow exceptions.
 * Fixed a bug in ManifoldBetweenTwoFaces that led to incorrect `ContactManifold::mRelativeContactPointsOn2` when the contact normal and the face normal were not roughly parallel. Also it led to possibly jitter in the simulation in that case.
 * Fixed a bug in ManifoldBetweenTwoFaces that led to incorrect `ContactManifold::mRelativeContactPointsOn2` when the contact normal and the face normal were not roughly parallel. Also it led to possibly jitter in the simulation in that case.
 * Fixed InternalEdgeRemovingCollector not working when colliding with a very dense triangle grid because it ran out of internal space. Now falling back to memory allocations when this happens to avoid ghost collisions.
 * Fixed InternalEdgeRemovingCollector not working when colliding with a very dense triangle grid because it ran out of internal space. Now falling back to memory allocations when this happens to avoid ghost collisions.
+* Fixed running out of stack space when simulating a really high number of active rigid bodies.
 
 
 ## v5.2.0
 ## v5.2.0
 
 

+ 1 - 1
Jolt/Core/TempAllocator.h

@@ -59,7 +59,7 @@ public:
 			uint new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
 			uint new_top = mTop + AlignUp(inSize, JPH_RVECTOR_ALIGNMENT);
 			if (new_top > mSize)
 			if (new_top > mSize)
 			{
 			{
-				Trace("TempAllocator: Out of memory");
+				Trace("TempAllocator: Out of memory trying to allocate %u bytes", inSize);
 				std::abort();
 				std::abort();
 			}
 			}
 			void *address = mBase + mTop;
 			void *address = mBase + mTop;

+ 3 - 1
Jolt/Physics/PhysicsSystem.cpp

@@ -844,6 +844,9 @@ void PhysicsSystem::JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int in
 	// (always start looking at results from the next job)
 	// (always start looking at results from the next job)
 	int read_queue_idx = (inJobIndex + 1) % ioStep->mBodyPairQueues.size();
 	int read_queue_idx = (inJobIndex + 1) % ioStep->mBodyPairQueues.size();
 
 
+	// Allocate space to temporarily store a batch of active bodies
+	BodyID *active_bodies = (BodyID *)JPH_STACK_ALLOC(cActiveBodiesBatchSize * sizeof(BodyID));
+
 	for (;;)
 	for (;;)
 	{
 	{
 		// Check if there are active bodies to be processed
 		// Check if there are active bodies to be processed
@@ -895,7 +898,6 @@ void PhysicsSystem::JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int in
 
 
 				// Copy active bodies to temporary array, broadphase will reorder them
 				// Copy active bodies to temporary array, broadphase will reorder them
 				uint32 batch_size = active_bodies_read_idx_end - active_bodies_read_idx;
 				uint32 batch_size = active_bodies_read_idx_end - active_bodies_read_idx;
-				BodyID *active_bodies = (BodyID *)JPH_STACK_ALLOC(batch_size * sizeof(BodyID));
 				memcpy(active_bodies, mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody) + active_bodies_read_idx, batch_size * sizeof(BodyID));
 				memcpy(active_bodies, mBodyManager.GetActiveBodiesUnsafe(EBodyType::RigidBody) + active_bodies_read_idx, batch_size * sizeof(BodyID));
 
 
 				// Find pairs in the broadphase
 				// Find pairs in the broadphase

+ 62 - 0
PerformanceTest/MaxBodiesScene.h

@@ -0,0 +1,62 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+// Jolt includes
+#include <Jolt/Physics/Collision/Shape/BoxShape.h>
+#include <Jolt/Physics/Body/BodyCreationSettings.h>
+
+// Local includes
+#include "PerformanceTestScene.h"
+#include "Layers.h"
+
+// A scene that creates the max number of bodies that Jolt supports and simulates them
+class MaxBodiesScene : public PerformanceTestScene
+{
+public:
+	virtual const char *	GetName() const override
+	{
+		return "MaxBodies";
+	}
+
+	virtual uint			GetTempAllocatorSizeMB() const override
+	{
+		return 256;
+	}
+
+	virtual uint			GetMaxBodies() const override
+	{
+		return BodyID::cMaxBodyIndex + 1;
+	}
+
+	virtual void			StartTest(PhysicsSystem &inPhysicsSystem, EMotionQuality inMotionQuality) override
+	{
+		BodyInterface &bi = inPhysicsSystem.GetBodyInterface();
+
+		// Reduce the solver iteration count in the interest of performance
+		PhysicsSettings settings = inPhysicsSystem.GetPhysicsSettings();
+		settings.mNumVelocitySteps = 4;
+		settings.mNumPositionSteps = 1;
+		inPhysicsSystem.SetPhysicsSettings(settings);
+
+		// Create the bodies
+		uint num_bodies = GetMaxBodies();
+		BodyIDVector body_ids;
+		body_ids.reserve(num_bodies);
+		uint num_per_axis = uint(pow(float(num_bodies), 1.0f / 3.0f)) + 1;
+		BodyCreationSettings bcs(new BoxShape(Vec3::sReplicate(0.5f)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
+		for (uint x = 0; x < num_per_axis && body_ids.size() < num_bodies; ++x)
+			for (uint y = 0; y < num_per_axis && body_ids.size() < num_bodies; ++y)
+				for (uint z = 0; z < num_per_axis && body_ids.size() < num_bodies; ++z)
+				{
+					bcs.mPosition = RVec3(Real(x), Real(y), Real(z));
+					body_ids.push_back(bi.CreateBody(bcs)->GetID());
+				}
+
+		// Add the bodies to the simulation
+		BodyInterface::AddState state = bi.AddBodiesPrepare(body_ids.data(), num_bodies);
+		bi.AddBodiesFinalize(body_ids.data(), num_bodies, state, EActivation::Activate);
+	}
+};

+ 1 - 0
PerformanceTest/PerformanceTest.cmake

@@ -12,6 +12,7 @@ set(PERFORMANCE_TEST_SRC_FILES
 	${PERFORMANCE_TEST_ROOT}/CharacterVirtualScene.h
 	${PERFORMANCE_TEST_ROOT}/CharacterVirtualScene.h
 	${PERFORMANCE_TEST_ROOT}/LargeMeshScene.h
 	${PERFORMANCE_TEST_ROOT}/LargeMeshScene.h
 	${PERFORMANCE_TEST_ROOT}/Layers.h
 	${PERFORMANCE_TEST_ROOT}/Layers.h
+	${PERFORMANCE_TEST_ROOT}/MaxBodiesScene.h
 )
 )
 
 
 # Group source files
 # Group source files

+ 5 - 2
PerformanceTest/PerformanceTest.cpp

@@ -46,6 +46,7 @@ JPH_SUPPRESS_WARNINGS
 #include "PyramidScene.h"
 #include "PyramidScene.h"
 #include "LargeMeshScene.h"
 #include "LargeMeshScene.h"
 #include "CharacterVirtualScene.h"
 #include "CharacterVirtualScene.h"
+#include "MaxBodiesScene.h"
 
 
 // Time step for physics
 // Time step for physics
 constexpr float cDeltaTime = 1.0f / 60.0f;
 constexpr float cDeltaTime = 1.0f / 60.0f;
@@ -119,6 +120,8 @@ int main(int argc, char** argv)
 				scene = unique_ptr<PerformanceTestScene>(new LargeMeshScene);
 				scene = unique_ptr<PerformanceTestScene>(new LargeMeshScene);
 			else if (strcmp(arg + 3, "CharacterVirtual") == 0)
 			else if (strcmp(arg + 3, "CharacterVirtual") == 0)
 				scene = unique_ptr<PerformanceTestScene>(new CharacterVirtualScene);
 				scene = unique_ptr<PerformanceTestScene>(new CharacterVirtualScene);
+			else if (strcmp(arg + 3, "MaxBodies") == 0)
+				scene = unique_ptr<MaxBodiesScene>(new MaxBodiesScene);
 			else
 			else
 			{
 			{
 				Trace("Invalid scene");
 				Trace("Invalid scene");
@@ -216,7 +219,7 @@ int main(int argc, char** argv)
 	RegisterTypes();
 	RegisterTypes();
 
 
 	// Create temp allocator
 	// Create temp allocator
-	TempAllocatorImpl temp_allocator(32 * 1024 * 1024);
+	TempAllocatorImpl temp_allocator(scene->GetTempAllocatorSizeMB() * 1024 * 1024);
 
 
 	// Show used instruction sets
 	// Show used instruction sets
 	Trace(GetConfigurationString());
 	Trace(GetConfigurationString());
@@ -306,7 +309,7 @@ int main(int argc, char** argv)
 
 
 				// Create physics system
 				// Create physics system
 				PhysicsSystem physics_system;
 				PhysicsSystem physics_system;
-				physics_system.Init(10240, 0, 65536, 20480, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
+				physics_system.Init(scene->GetMaxBodies(), 0, 65536, 20480, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
 
 
 				// Start test scene
 				// Start test scene
 				scene->StartTest(physics_system, motion_quality);
 				scene->StartTest(physics_system, motion_quality);

+ 6 - 0
PerformanceTest/PerformanceTestScene.h

@@ -14,6 +14,12 @@ public:
 	// Get name of test for debug purposes
 	// Get name of test for debug purposes
 	virtual const char *	GetName() const = 0;
 	virtual const char *	GetName() const = 0;
 
 
+	// Get the number of MB that the temp allocator should preallocate
+	virtual uint			GetTempAllocatorSizeMB() const						{ return 32; }
+
+	// Get the max number of bodies to support in the physics system
+	virtual uint			GetMaxBodies() const								{ return 10240; }
+
 	// Load assets for the scene
 	// Load assets for the scene
 	virtual bool			Load([[maybe_unused]] const String &inAssetPath)	{ return true; }
 	virtual bool			Load([[maybe_unused]] const String &inAssetPath)	{ return true; }
 
 

+ 3 - 2
TestFramework/Renderer/DX12/RenderInstancesDX12.cpp

@@ -20,13 +20,14 @@ void RenderInstancesDX12::Clear()
 
 
 void RenderInstancesDX12::CreateBuffer(int inNumInstances, int inInstanceSize)
 void RenderInstancesDX12::CreateBuffer(int inNumInstances, int inInstanceSize)
 {
 {
-	if (mInstanceBuffer == nullptr || mInstanceBufferSize < inNumInstances * inInstanceSize)
+	uint new_size = uint(inNumInstances) * inInstanceSize;
+	if (mInstanceBuffer == nullptr || mInstanceBufferSize < new_size)
 	{
 	{
 		// Delete the old buffer
 		// Delete the old buffer
 		Clear();
 		Clear();
 
 
 		// Calculate size
 		// Calculate size
-		mInstanceBufferSize = inNumInstances * inInstanceSize;
+		mInstanceBufferSize = new_size;
 
 
 		// Create buffer
 		// Create buffer
 		mInstanceBuffer = mRenderer->CreateD3DResourceOnUploadHeap(mInstanceBufferSize);
 		mInstanceBuffer = mRenderer->CreateD3DResourceOnUploadHeap(mInstanceBufferSize);

+ 1 - 1
TestFramework/Renderer/DX12/RenderInstancesDX12.h

@@ -32,6 +32,6 @@ private:
 	RendererDX12 *			mRenderer;
 	RendererDX12 *			mRenderer;
 
 
 	ComPtr<ID3D12Resource>	mInstanceBuffer;
 	ComPtr<ID3D12Resource>	mInstanceBuffer;
-	int						mInstanceBufferSize = 0;
+	uint					mInstanceBufferSize = 0;
 	int						mInstanceSize = 0;
 	int						mInstanceSize = 0;
 };
 };