Prevent physics death spiral by falling back to true delta time when needed

pull/3057/head
wareya 4 years ago committed by psi29a
parent 86bd173d69
commit 63f01d8c5f

@ -57,6 +57,7 @@
Bug #5424: Creatures do not headtrack player Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading Bug #5427: GetDistance unknown ID error is misleading
Bug #5431: Physics performance degradation after a specific number of actors on a scene
Bug #5435: Enemies can't hurt the player when collision is off Bug #5435: Enemies can't hurt the player when collision is off
Bug #5441: Enemies can't push a player character when in critical strike stance Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster Bug #5451: Magic projectiles don't disappear with the caster
@ -143,6 +144,7 @@
Feature #5730: Add graphic herbalism option to the launcher and documents Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used. Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support Feature #5813: Instanced groundcover support
Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation Task #5520: Improve cell name autocompleter implementation
Task #5844: Update 'toggle sneak' documentation Task #5844: Update 'toggle sneak' documentation

@ -101,7 +101,7 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{ {
const float interpolationFactor = timeAccum / physicsDt; const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor); return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
} }
@ -138,7 +138,8 @@ namespace
namespace MWPhysics namespace MWPhysics
{ {
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld) PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
: mPhysicsDt(physicsDt) : mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt)
, mTimeAccum(0.f) , mTimeAccum(0.f)
, mCollisionWorld(std::move(collisionWorld)) , mCollisionWorld(std::move(collisionWorld))
, mNumJobs(0) , mNumJobs(0)
@ -152,6 +153,11 @@ namespace MWPhysics
, mNextLOS(0) , mNextLOS(0)
, mFrameNumber(0) , mFrameNumber(0)
, mTimer(osg::Timer::instance()) , mTimer(osg::Timer::instance())
, mPrevStepCount(1)
, mBudget(physicsDt)
, mAsyncBudget(0.0f)
, mBudgetCursor(0)
, mAsyncStartTime(0)
, mTimeBegin(0) , mTimeBegin(0)
, mTimeEnd(0) , mTimeEnd(0)
, mFrameStart(0) , mFrameStart(0)
@ -220,13 +226,61 @@ namespace MWPhysics
thread.join(); thread.join();
} }
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
{
int maxAllowedSteps = 2;
int numSteps = timeAccum / mDefaultPhysicsDt;
// adjust maximum step count based on whether we're likely physics bottlenecked or not
// if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
// if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
// if it ends up lower than numSteps and also 1, we will run a single delta time physics step
// if we did not do this, and had a fixed step count limit,
// we would have an unnecessarily low render framerate if we were only physics bottlenecked,
// and we would be unnecessarily invoking true delta time if we were only render bottlenecked
// get physics timing stats
float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
// time spent per step in terms of the intended physics framerate
budgetMeasurement /= mDefaultPhysicsDt;
// ensure sane minimum value
budgetMeasurement = std::max(0.00001f, budgetMeasurement);
// we're spending almost or more than realtime per physics frame; limit to a single step
if (budgetMeasurement > 0.95)
maxAllowedSteps = 1;
// physics is fairly cheap; limit based on expense
if (budgetMeasurement < 0.5)
maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
// limit to a reasonable amount
maxAllowedSteps = std::min(10, maxAllowedSteps);
// fall back to delta time for this frame if fixed timestep physics would fall behind
float actualDelta = mDefaultPhysicsDt;
if (numSteps > maxAllowedSteps)
{
numSteps = maxAllowedSteps;
// ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
// this causes interpolation to 100% use the most recent physics result when true delta time is happening
// and we deliberately simulate up to exactly the timestamp that we want to render
actualDelta = timeAccum/float(numSteps+1);
// actually: if this results in a per-step delta less than the target physics steptime, clamp it
// this might reintroduce some stutter, but only comes into play in obscure cases
// (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
}
return std::make_tuple(numSteps, actualDelta);
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{ {
// This function run in the main thread. // This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run. // While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
double timeStart = mTimer->tick();
mMovedActors.clear(); mMovedActors.clear();
// start by finishing previous background computation // start by finishing previous background computation
@ -251,14 +305,21 @@ namespace MWPhysics
mMovedActors.emplace_back(data.mActorRaw->getPtr()); mMovedActors.emplace_back(data.mActorRaw->getPtr());
} }
} }
if(mAdvanceSimulation)
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
updateStats(frameStart, frameNumber, stats); updateStats(frameStart, frameNumber, stats);
} }
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
timeAccum -= numSteps*newDelta;
// init // init
for (auto& data : actorsData) for (auto& data : actorsData)
data.updatePosition(); data.updatePosition();
mPrevStepCount = numSteps;
mRemainingSteps = numSteps; mRemainingSteps = numSteps;
mTimeAccum = timeAccum; mTimeAccum = timeAccum;
mPhysicsDt = newDelta;
mActorsFrameData = std::move(actorsData); mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0); mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true; mNewFrame = true;
@ -269,20 +330,30 @@ namespace MWPhysics
if (mAdvanceSimulation) if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>(); mWorldFrameData = std::make_unique<WorldFrameData>();
if (mAdvanceSimulation)
mBudgetCursor += 1;
if (mNumThreads == 0) if (mNumThreads == 0)
{ {
syncComputation(); syncComputation();
if(mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
return mMovedActors; return mMovedActors;
} }
mAsyncStartTime = mTimer->tick();
lock.unlock(); lock.unlock();
mHasJob.notify_all(); mHasJob.notify_all();
if (mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
return mMovedActors; return mMovedActors;
} }
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{ {
std::unique_lock lock(mSimulationMutex); std::unique_lock lock(mSimulationMutex);
mBudget.reset(mDefaultPhysicsDt);
mAsyncBudget.reset(0.0f);
mMovedActors.clear(); mMovedActors.clear();
mActorsFrameData.clear(); mActorsFrameData.clear();
for (const auto& [_, actor] : actors) for (const auto& [_, actor] : actors)

@ -13,6 +13,7 @@
#include "physicssystem.hpp" #include "physicssystem.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
#include "components/misc/budgetmeasurement.hpp"
namespace Misc namespace Misc
{ {
@ -32,7 +33,7 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements /// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions /// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor /// @return new position of each actor
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); const std::vector<MWWorld::Ptr>& moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors); const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
@ -58,11 +59,13 @@ namespace MWPhysics
void updateAabbs(); void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr); void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::tuple<int, float> calculateStepConfig(float timeAccum) const;
std::unique_ptr<WorldFrameData> mWorldFrameData; std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData; std::vector<ActorFrameData> mActorsFrameData;
std::vector<MWWorld::Ptr> mMovedActors; std::vector<MWWorld::Ptr> mMovedActors;
const float mPhysicsDt; float mDefaultPhysicsDt;
float mPhysicsDt;
float mTimeAccum; float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld; std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::vector<LOSRequest> mLOSCache; std::vector<LOSRequest> mLOSCache;
@ -94,6 +97,12 @@ namespace MWPhysics
unsigned int mFrameNumber; unsigned int mFrameNumber;
const osg::Timer* mTimer; const osg::Timer* mTimer;
int mPrevStepCount;
Misc::BudgetMeasurement mBudget;
Misc::BudgetMeasurement mAsyncBudget;
unsigned int mBudgetCursor;
osg::Timer_t mAsyncStartTime;
osg::Timer_t mTimeBegin; osg::Timer_t mTimeBegin;
osg::Timer_t mTimeEnd; osg::Timer_t mTimeEnd;
osg::Timer_t mFrameStart; osg::Timer_t mFrameStart;

@ -744,19 +744,14 @@ namespace MWPhysics
{ {
mTimeAccum += dt; mTimeAccum += dt;
const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation) if (skipSimulation)
return mTaskScheduler->resetSimulation(mActors); return mTaskScheduler->resetSimulation(mActors);
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats); // modifies mTimeAccum
return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
} }
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps) std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
{ {
std::vector<ActorFrameData> actorsFrameData; std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size()); actorsFrameData.reserve(mMovementQueue.size());
@ -796,7 +791,7 @@ namespace MWPhysics
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value. // Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
MWWorld::Ptr standingOn; MWWorld::Ptr standingOn;
if (numSteps == 0) if (!willSimulate)
standingOn = physicActor->getStandingOnPtr(); standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel); actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);

@ -252,7 +252,7 @@ namespace MWPhysics
void updateWater(); void updateWater();
std::vector<ActorFrameData> prepareFrameData(int numSteps); std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue; osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;

@ -0,0 +1,42 @@
#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
#define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
namespace Misc
{
class BudgetMeasurement
{
std::array<float, 4> mBudgetHistory;
std::array<unsigned int, 4> mBudgetStepCount;
public:
BudgetMeasurement(const float default_expense)
{
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
mBudgetStepCount = {1, 1, 1, 1};
}
void reset(const float default_expense)
{
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
mBudgetStepCount = {1, 1, 1, 1};
}
void update(double delta, unsigned int stepCount, size_t cursor)
{
mBudgetHistory[cursor%4] = delta;
mBudgetStepCount[cursor%4] = stepCount;
}
double get() const
{
float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]);
unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]);
return sum/float(stepCountSum);
}
};
}
#endif
Loading…
Cancel
Save