Hi folks!
I’ve been playing with the dynamic origin functionality PhysX provides and noticed my simulation ticks and origin shifts gradually start to take longer if I shift the origin a lot.
I’m calling shiftOrigin every frame - don’t worry I’m not going to do this in my final application, it was just so I could quickly benchmark the performance of the function. If I leave it running for a few minutes then gradually the simulate/fetchResults calls take longer settling at around twice their original time. The call to shiftOrigin also slows down settling at around 7 times it’s original time span - measured using the windows high performance timer.
The scene is reasonably complex so I could get meaningful numbers - there’s 550 cubes arranged in 10 stacks of 55, a rigid body with a compound collision shape, a simple triangle mesh for the ground and some planes to keep everything boxed in. I also run a fully fixed time step (60Hz) for reproduce-ability. I also ran in release!
I’ve also repeated the test with the same scene and logic except I only called shiftOrigin every 5 seconds instead of every frame and left it running for 14+ hours (so it was about the same number of calls to shiftOrigin in total as my stress tests made). The slow down wasn’t present in this test - in-fact the shiftOrigin call got slightly faster over time (but only by 20 microseconds).
I’m not really sure what could be going on that degrades the performance so gradually - I would’ve thought the time taken to complete shiftOrigin would be quite stable when the numbers of objects isn’t changing. Anyway - I thought I’d make a post to get feedback/make people aware of this.
I’m probably being an idiot somewhere in my test, so I’ve extracted my little test class .h and .cpp for anyone to have a go with. All you need to do is initialise it, and call update in a loop passing in time. It will deal with the rest.
.h
#pragma once
#include "DXUT.h"
#include "PxPhysicsAPI.h"
#include <fstream>
#include <iostream>
struct MaterialUserData
{
int m_someData;
};
class SimplePhysXManager
{
public:
SimplePhysXManager();
~SimplePhysXManager();
bool Initialise( bool sendDebugToFile );
void Update( double totalGameTime, float elapsedFrameTime );
void ShutDown();
private:
void SetupVisualDebugger();
void CreatePhysicsWorldAndObjects();
void MakeTheGround();
void WriteOutFrameTimeUpdate( double gameTime, LARGE_INTEGER timeOut );
void WriteOutFrameTimeShift( double gameTime, LARGE_INTEGER timeOut );
physx::PxRigidDynamic* createDynamic( const physx::PxTransform& t, const physx::PxGeometry& geometry, physx::PxU32 collisionGroup, const physx::PxVec3& velocity = physx::PxVec3( 0 ) );
void SimplePhysXManager::createStack( const physx::PxTransform& t, physx::PxU32 size, physx::PxReal halfExtent );
public:
static physx::PxDefaultErrorCallback s_defaultPhysXErrorCallback;
static physx::PxDefaultAllocator s_defaultAllocatorCallback;
public:
physx::PxFoundation* m_foundationObject;
physx::PxProfileZoneManager* m_profileZoneManager;
physx::PxPhysics* m_physics;
physx::PxCooking* m_cooking;
physx::PxDefaultCpuDispatcher* m_defaultCpuDispatcher;
physx::PxVisualDebugger* m_visualDebugger;
physx::PxVisualDebuggerConnection* m_visualDebuggerConnection;
physx::debugger::renderer::PvdUserRenderer* m_debugRenderer;
physx::PxScene* m_scene;
physx::PxMaterial* m_material;
physx::PxRigidStatic* m_groundPlane;
float m_accumulatedTime;
bool m_sendDebugToFile;
MaterialUserData m_materialUserDatas[ 4 ];
std::ofstream outputFileUpdate;
std::ofstream outputFileShift;
LARGE_INTEGER lastWroteToFile;
LARGE_INTEGER lastWroteToFileUpdate;
};
.cpp
#include "SimplePhysXManager.h"
#include "physxvisualdebuggersdk/PvdUserRenderer.h"
using namespace physx;
PxDefaultErrorCallback SimplePhysXManager::s_defaultPhysXErrorCallback;
PxDefaultAllocator SimplePhysXManager::s_defaultAllocatorCallback;
SimplePhysXManager::SimplePhysXManager( )
: m_foundationObject( nullptr )
, m_profileZoneManager( nullptr )
, m_physics( nullptr )
, m_cooking( nullptr )
, m_defaultCpuDispatcher( nullptr )
, m_visualDebugger( nullptr )
, m_visualDebuggerConnection( nullptr )
, m_debugRenderer( nullptr )
, m_scene( nullptr )
, m_material( nullptr )
, m_groundPlane( nullptr )
, m_accumulatedTime( 0.0f )
, m_sendDebugToFile( false )
{
}
SimplePhysXManager::~SimplePhysXManager(void)
{
}
bool SimplePhysXManager::Initialise( bool sendDebugToFile )
{
bool succesfullyInitialised = true;
m_sendDebugToFile = sendDebugToFile;
m_foundationObject = PxCreateFoundation( PX_PHYSICS_VERSION, s_defaultAllocatorCallback, s_defaultPhysXErrorCallback );
if( m_foundationObject )
{
m_profileZoneManager = &PxProfileZoneManager::createProfileZoneManager( m_foundationObject );
bool recordMemoryAllocations = true;
m_physics = PxCreatePhysics( PX_PHYSICS_VERSION, *m_foundationObject, PxTolerancesScale(), recordMemoryAllocations, m_profileZoneManager );
m_cooking = PxCreateCooking( PX_PHYSICS_VERSION, *m_foundationObject, PxCookingParams( m_physics->getTolerancesScale() ) );
SetupVisualDebugger();
CreatePhysicsWorldAndObjects();
}
outputFileUpdate.open( "C:\\PhysXExperiment\\test-update.csv", std::ios::app );
outputFileShift.open( "C:\\PhysXExperiment\\test-shift_real.csv", std::ios::app );
QueryPerformanceCounter(&lastWroteToFile);
QueryPerformanceCounter(&lastWroteToFileUpdate);
return m_foundationObject
&& m_profileZoneManager
&& m_physics
&& m_scene
//&& m_visualDebugger
//&& m_visualDebuggerConnection
&& m_material
&& m_defaultCpuDispatcher
&& m_groundPlane;
//&& ( m_debugRenderer || m_sendDebugToFile );
}
void SimplePhysXManager::WriteOutFrameTimeUpdate( double gameTime, LARGE_INTEGER timeOut )
{
outputFileUpdate << "\n" << gameTime << "," << timeOut.QuadPart;
outputFileUpdate.flush();
}
void SimplePhysXManager::WriteOutFrameTimeShift( double gameTime, LARGE_INTEGER timeOut )
{
outputFileShift << "\n" << gameTime << "," << timeOut.QuadPart;
outputFileShift.flush();
}
void SimplePhysXManager::Update( double totalGameTime, float elapsedFrameTime )
{
LARGE_INTEGER ElapsedSimulationTime;
LARGE_INTEGER ElapsedShiftTime;
ElapsedSimulationTime.QuadPart = 0;
ElapsedShiftTime.QuadPart = 0;
float maximumFrameTime = 1.0f / 60.0f;
m_accumulatedTime += elapsedFrameTime;
// work out how much time we're going to consume
int numberOfIterationsNeeded = ( int )( m_accumulatedTime / maximumFrameTime );
// remove that from the accumulator
m_accumulatedTime -= ( ( float )numberOfIterationsNeeded * maximumFrameTime );
// consume the time
for( int i = 0; i < numberOfIterationsNeeded; ++i )
{
LARGE_INTEGER Frequency;
QueryPerformanceFrequency(&Frequency);
//////////////////////////////////////////////////////////////////////////
// Benchmark the frame update
LARGE_INTEGER StartingTime, EndingTime;//, ElapsedMicroseconds;
QueryPerformanceCounter(&StartingTime);
//////////////////////////////////////////////////////////////////////////
m_scene->simulate( maximumFrameTime );
m_scene->fetchResults( true );
//////////////////////////////////////////////////////////////////////////
QueryPerformanceCounter(&EndingTime);
ElapsedSimulationTime.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedSimulationTime.QuadPart *= 1000000;
ElapsedSimulationTime.QuadPart /= Frequency.QuadPart;
if( ElapsedSimulationTime.QuadPart != 0 )
{
WCHAR message[256];
swprintf_s(message, L"Frame update time: %d\n", ElapsedSimulationTime.QuadPart );
OutputDebugString( message );
}
//////////////////////////////////////////////////////////////////////////
// Benchmark the position update
// LONGTERMTIMER
//LARGE_INTEGER CurrentTime;
//LARGE_INTEGER ElapsedMicrosecondsFrame;
//QueryPerformanceCounter(&CurrentTime);
//
//ElapsedMicrosecondsFrame.QuadPart = CurrentTime.QuadPart - lastWroteToFile.QuadPart;
//
//ElapsedMicrosecondsFrame.QuadPart *= 1000000;
//ElapsedMicrosecondsFrame.QuadPart /= Frequency.QuadPart;
//
//bool shiftOrigin = false;
//
//if( ElapsedMicrosecondsFrame.QuadPart > 5000000 )
//{
// lastWroteToFile.QuadPart = CurrentTime.QuadPart;
// shiftOrigin = true;
//}
//
//ElapsedMicrosecondsFrame.QuadPart = CurrentTime.QuadPart - lastWroteToFileUpdate.QuadPart;
//
//ElapsedMicrosecondsFrame.QuadPart *= 1000000;
//ElapsedMicrosecondsFrame.QuadPart /= Frequency.QuadPart;
//
//if( ElapsedMicrosecondsFrame.QuadPart > 1000000 )
//{
// lastWroteToFileUpdate.QuadPart = CurrentTime.QuadPart;
// WriteOutFrameTimeUpdate( totalGameTime, ElapsedSimulationTime );
//}
QueryPerformanceCounter(&StartingTime);
// LONGTERMTIMER
//if( shiftOrigin )
{
m_scene->shiftOrigin( PxVec3( 0.0f, 0.0f, 0.0f ) );
}
//////////////////////////////////////////////////////////////////////////
QueryPerformanceCounter(&EndingTime);
ElapsedShiftTime.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedShiftTime.QuadPart *= 1000000;
ElapsedShiftTime.QuadPart /= Frequency.QuadPart;
// LONGTERMTIMER
//if( shiftOrigin )
//{
// WriteOutFrameTimeShift( totalGameTime, ElapsedShiftTime );
//}
if( ElapsedShiftTime.QuadPart != 0 )
{
WCHAR message[256];
swprintf_s(message, L"Origin shift time: %d\n", ElapsedShiftTime.QuadPart );
OutputDebugString( message );
}
}
}
void SimplePhysXManager::ShutDown()
{
outputFileUpdate.flush();
outputFileUpdate.close();
outputFileShift.flush();
outputFileShift.close();
if( m_visualDebuggerConnection )
{
m_visualDebuggerConnection->release();
}
if( m_physics )
{
m_physics->release();
}
if( m_profileZoneManager )
{
m_profileZoneManager->release();
}
if( m_foundationObject )
{
m_foundationObject->release();
}
}
void SimplePhysXManager::SetupVisualDebugger()
{
if( m_physics && m_physics->getPvdConnectionManager() )
{
m_visualDebugger = m_physics->getVisualDebugger();
m_visualDebugger->setVisualizeConstraints( true );
m_visualDebugger->setVisualDebuggerFlag( PxVisualDebuggerFlag::eTRANSMIT_SCENEQUERIES, true );
if( m_sendDebugToFile )
{
m_visualDebuggerConnection = PxVisualDebuggerExt::createConnection( m_physics->getPvdConnectionManager(), "C:\\PhysXOutput\\myTestOutput.pxd2" );
}
else
{
m_visualDebuggerConnection = PxVisualDebuggerExt::createConnection( m_physics->getPvdConnectionManager(), "127.0.0.1", 5425, 10 );
if( m_visualDebuggerConnection )
{
m_debugRenderer = &m_visualDebuggerConnection->createRenderer();
}
}
}
}
void SimplePhysXManager::CreatePhysicsWorldAndObjects()
{
/************************************************************************/
/* Make the scene */
/************************************************************************/
PxSceneDesc sceneDescription( m_physics->getTolerancesScale() );
sceneDescription.gravity = PxVec3( 0.0f, -9.81f, 0.0f );
m_defaultCpuDispatcher = PxDefaultCpuDispatcherCreate( 6 );
sceneDescription.cpuDispatcher = m_defaultCpuDispatcher;
sceneDescription.filterShader = PxDefaultSimulationFilterShader;
m_scene = m_physics->createScene( sceneDescription );
/************************************************************************/
/* Make a material */
/************************************************************************/
m_material = m_physics->createMaterial( 0.5f, 0.5f, 0.6f );
if( m_material && m_scene )
{
/************************************************************************/
/* Make a ground object */
/************************************************************************/
MakeTheGround();
/************************************************************************/
/* Make some stacks and a projectile to knock them over */
/************************************************************************/
PxReal stackZ = 10.0f;
for( PxU32 i = 0; i < 10; i++ )
{
createStack( PxTransform( PxVec3( 0, 0, stackZ -= 10.0f ) ), 10, 2.0f );
}
PxRigidDynamic* rigidBody = createDynamic( PxTransform( PxVec3( 0, 11.5f, stackZ -= 50.0f ) ), PxSphereGeometry( 5.0f ), 1, PxVec3( 0, 0, 125.0f) );
rigidBody->setName( "Projectile rigid body" );
rigidBody->setAngularVelocity( PxVec3( 5.0f, 5.0f, 5.0f ) );
PxFilterData filterData( 2, 0, 0, 0 );
PxShape* nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( 0.0f, 5.0f, 0.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "Y+" );
nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( 0.0f, -5.0f, 0.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "Y-" );
nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( 5.0f, 0.0f, 0.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "X+" );
nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( -5.0f, 0.0f, 0.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "X-" );
nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( 0.0f, 0.0f, 5.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "Z+" );
filterData.word0 = 1;
nextShape = rigidBody->createShape( PxSphereGeometry( 3.0f ), *m_material, PxTransform( PxVec3( 0.0f, 0.0f, -5.0f ) ) );
nextShape->setSimulationFilterData( filterData );
nextShape->setName( "Z-" );
}
}
void SimplePhysXManager::MakeTheGround()
{
PxFilterData filterData( 1, 0, 0, 0 );
float scale = 100.0f;
PxShape* shapes[ 5 ];
shapes[ 0 ] = nullptr;
shapes[ 1 ] = nullptr;
shapes[ 2 ] = nullptr;
shapes[ 3 ] = nullptr;
shapes[ 4 ] = nullptr;
// create some boundaries
PxRigidStatic* positiveXSide = PxCreatePlane(*m_physics, PxPlane( PxVec3( 1.0f, 0.0f, 0.0f ), scale ), *m_material );
m_scene->addActor( *positiveXSide );
positiveXSide->getShapes( shapes, 5 );
shapes[ 0 ]->setSimulationFilterData( filterData );
PxRigidStatic* negativeXSide = PxCreatePlane(*m_physics, PxPlane( PxVec3( -1.0f, 0.0f, 0.0f ), scale ), *m_material );
m_scene->addActor( *negativeXSide );
negativeXSide->getShapes( shapes, 5 );
shapes[ 0 ]->setSimulationFilterData( filterData );
PxRigidStatic* positiveZSide = PxCreatePlane(*m_physics, PxPlane( PxVec3( 0.0f, 0.0f, 1.0f ), scale ), *m_material );
m_scene->addActor( *positiveZSide );
positiveZSide->getShapes( shapes, 5 );
shapes[ 0 ]->setSimulationFilterData( filterData );
PxRigidStatic* negativeZSide = PxCreatePlane(*m_physics, PxPlane( PxVec3( 0.0f, 0.0f, -1.0f ), scale ), *m_material );
m_scene->addActor( *negativeZSide );
negativeZSide->getShapes( shapes, 5 );
shapes[ 0 ]->setSimulationFilterData( filterData );
PxRigidStatic* negativeYSide = PxCreatePlane(*m_physics, PxPlane( PxVec3( 0.0f, -1.0f, 0.0f ), scale ), *m_material );
m_scene->addActor( *negativeYSide );
negativeYSide->getShapes( shapes, 5 );
shapes[ 0 ]->setSimulationFilterData( filterData );
// create the ground geometry
PxVec3 meshVertices[ 9 ];
meshVertices[ 0 ].x = 0.0f * scale;
meshVertices[ 0 ].y = 0.0f * scale;
meshVertices[ 0 ].z = 0.0f * scale;
meshVertices[ 1 ].x = 1.0f * scale;
meshVertices[ 1 ].y = 0.0f * scale;
meshVertices[ 1 ].z = 0.0f * scale;
meshVertices[ 2 ].x = 2.0f * scale;
meshVertices[ 2 ].y = 0.0f * scale;
meshVertices[ 2 ].z = 0.0f * scale;
meshVertices[ 3 ].x = 0.0f * scale;
meshVertices[ 3 ].y = 0.0f * scale;
meshVertices[ 3 ].z = 1.0f * scale;
meshVertices[ 4 ].x = 1.0f * scale;
meshVertices[ 4 ].y = -0.5f * scale;
meshVertices[ 4 ].z = 1.0f * scale;
meshVertices[ 5 ].x = 2.0f * scale;
meshVertices[ 5 ].y = 0.0f * scale;
meshVertices[ 5 ].z = 1.0f * scale;
meshVertices[ 6 ].x = 0.0f * scale;
meshVertices[ 6 ].y = 0.0f * scale;
meshVertices[ 6 ].z = 2.0f * scale;
meshVertices[ 7 ].x = 1.0f * scale;
meshVertices[ 7 ].y = 0.0f * scale;
meshVertices[ 7 ].z = 2.0f * scale;
meshVertices[ 8 ].x = 2.0f * scale;
meshVertices[ 8 ].y = 0.0f * scale;
meshVertices[ 8 ].z = 2.0f * scale;
PxU32 triangleIndices[ 24 ];
triangleIndices[ 0 ] = 3;
triangleIndices[ 1 ] = 1;
triangleIndices[ 2 ] = 0;
triangleIndices[ 3 ] = 4;
triangleIndices[ 4 ] = 1;
triangleIndices[ 5 ] = 3;
triangleIndices[ 6 ] = 4;
triangleIndices[ 7 ] = 2;
triangleIndices[ 8 ] = 1;
triangleIndices[ 9 ] = 5;
triangleIndices[ 10 ] = 2;
triangleIndices[ 11 ] = 4;
triangleIndices[ 12 ] = 6;
triangleIndices[ 13 ] = 4;
triangleIndices[ 14 ] = 3;
triangleIndices[ 15 ] = 7;
triangleIndices[ 16 ] = 4;
triangleIndices[ 17 ] = 6;
triangleIndices[ 18 ] = 7;
triangleIndices[ 19 ] = 5;
triangleIndices[ 20 ] = 4;
triangleIndices[ 21 ] = 8;
triangleIndices[ 22 ] = 5;
triangleIndices[ 23 ] = 7;
PxMaterialTableIndex materialIndices[ 8 ];
materialIndices[ 0 ] = 0;
materialIndices[ 1 ] = 0;
materialIndices[ 2 ] = 1;
materialIndices[ 3 ] = 1;
materialIndices[ 4 ] = 2;
materialIndices[ 5 ] = 2;
materialIndices[ 6 ] = 3;
materialIndices[ 7 ] = 3;
m_materialUserDatas[ 0 ].m_someData = 1;
m_materialUserDatas[ 1 ].m_someData = 2;
m_materialUserDatas[ 2 ].m_someData = 3;
m_materialUserDatas[ 3 ].m_someData = 4;
PxMaterial* materialList[ 4 ];
materialList[ 0 ] = m_physics->createMaterial( 0.1f, 0.1f, 0.0f );
materialList[ 0 ]->userData = &m_materialUserDatas[ 0 ];
materialList[ 1 ] = m_physics->createMaterial( 0.2f, 0.2f, 0.33f );
materialList[ 1 ]->userData = &m_materialUserDatas[ 1 ];
materialList[ 2 ] = m_physics->createMaterial( 0.3f, 0.3f, 0.66f );
materialList[ 2 ]->userData = &m_materialUserDatas[ 2 ];
materialList[ 3 ] = m_physics->createMaterial( 0.4f, 0.4f, 1.0f );
materialList[ 3 ]->userData = &m_materialUserDatas[ 3 ];
PxTriangleMeshDesc meshDescription;
meshDescription.points.data = meshVertices;
meshDescription.points.count = 9;
meshDescription.points.stride = sizeof( PxVec3 );
meshDescription.triangles.data = triangleIndices;
meshDescription.triangles.count = 8;
meshDescription.triangles.stride = 3 * sizeof( PxU32 );
meshDescription.materialIndices.data = materialIndices;
meshDescription.materialIndices.stride = sizeof( PxMaterialTableIndex );
PxTriangleMesh* groundTriangleMesh = m_cooking->createTriangleMesh( meshDescription, m_physics->getPhysicsInsertionCallback() );
m_groundPlane = m_physics->createRigidStatic( PxTransform( PxVec3( -scale, 0.0f, -scale ) ) );
m_scene->addActor( *m_groundPlane );
PxTriangleMeshGeometry triangleMeshGeometry( groundTriangleMesh );
PxShape* shape = m_groundPlane->createShape( triangleMeshGeometry, materialList, 4, PxTransform( PxIdentity ) );
shape->setSimulationFilterData( filterData );
}
PxRigidDynamic* SimplePhysXManager::createDynamic( const PxTransform& t, const PxGeometry& geometry, PxU32 collisionGroup, const PxVec3& velocity )
{
PxRigidDynamic* dynamic = m_physics->createRigidDynamic( t );
dynamic->setAngularDamping( 0.5f );
dynamic->setLinearVelocity( velocity );
dynamic->setMass( 500.0f );
PxFilterData filterData( collisionGroup, 0, 0, 0 );
PxShape* shape = dynamic->createShape( geometry, *m_material, PxTransform( PxIdentity ) );
shape->setSimulationFilterData( filterData );
m_scene->addActor( *dynamic );
return dynamic;
}
void SimplePhysXManager::createStack( const PxTransform& t, PxU32 size, PxReal halfExtent )
{
PxShape* shape = m_physics->createShape( PxBoxGeometry( halfExtent, halfExtent, halfExtent ), *m_material );
PxFilterData filterData( 1, 0, 0, 0 );
shape->setSimulationFilterData( filterData );
for( PxU32 i = 0; i < size; i++ )
{
for( PxU32 j = 0; j < size - i; j++ )
{
PxTransform localTm( PxVec3( PxReal( j * 2 ) - PxReal( size - i ), PxReal( i * 2 + 1 ), 0 ) * halfExtent );
PxRigidDynamic* body = m_physics->createRigidDynamic( t.transform( localTm ) );
body->setMass( 10.0f );
body->attachShape( *shape );
PxRigidBodyExt::updateMassAndInertia( *body, 10.0f );
m_scene->addActor( *body );
}
}
shape->release();
}