Browse Source

Fixed physics transform interpolation when physics timestep is lower than framerate.
Use high resolution timer for frame timestep to avoid jitter at vsync frequency.
Rewrote frame limiting.
Elapsed time API changed.

Lasse Öörni 14 years ago
parent
commit
ff9df846d1

+ 1 - 2
Docs/ScriptAPI.dox

@@ -746,8 +746,7 @@ Properties:<br>
 - String& typeName (readonly)
 - uint frameNumber (readonly)
 - float timeStep (readonly)
-- uint timeStepMSec (readonly)
-- uint totalMSec (readonly)
+- float elapsedTime (readonly)
 
 
 Log

+ 7 - 7
Engine/Core/Timer.cpp

@@ -44,8 +44,6 @@ Time::Time(Context* context) :
     Object(context),
     frameNumber_(0),
     timeStep_(0.0f),
-    timeStepMSec_(0),
-    totalMSec_(0),
     timerPeriod_(0)
 {
     #ifdef WIN32
@@ -66,14 +64,13 @@ Time::~Time()
     SetTimerPeriod(0);
 }
 
-void Time::BeginFrame(unsigned mSec)
+void Time::BeginFrame(float timeStep)
 {
     ++frameNumber_;
     if (!frameNumber_)
         ++frameNumber_;
     
-    timeStep_ = (float)mSec / 1000.0f;
-    timeStepMSec_ = mSec;
+    timeStep_ = timeStep;
     
     // Frame begin event
     using namespace BeginFrame;
@@ -86,8 +83,6 @@ void Time::BeginFrame(unsigned mSec)
 
 void Time::EndFrame()
 {
-    totalMSec_ += timeStepMSec_;
-    
     // Frame end event
     SendEvent(E_ENDFRAME);
 }
@@ -105,6 +100,11 @@ void Time::SetTimerPeriod(unsigned mSec)
     #endif
 }
 
+float Time::GetElapsedTime()
+{
+    return elapsedTime_.GetMSec(false) / 1000.0f;
+}
+
 void Time::Sleep(unsigned mSec)
 {
     #ifdef WIN32

+ 41 - 46
Engine/Core/Timer.h

@@ -25,52 +25,6 @@
 
 #include "Object.h"
 
-/// %Time and frame counter subsystem.
-class Time : public Object
-{
-    OBJECT(Time);
-    
-public:
-    /// Construct.
-    Time(Context* context);
-    /// Destruct. Reset the low-resolution timer period if set.
-    virtual ~Time();
-    
-    /// Begin new frame, with (last) frame duration in milliseconds and send frame start event.
-    void BeginFrame(unsigned mSec);
-    /// End frame. Increment total time and send frame end event.
-    void EndFrame();
-    /// %Set the low-resolution timer period in milliseconds. 0 resets to the default period.
-    void SetTimerPeriod(unsigned mSec);
-    
-    /// Return frame number, starting from 1 once BeginFrame() is called for the first time.
-    unsigned GetFrameNumber() const { return frameNumber_; }
-    /// Return current frame timestep as seconds.
-    float GetTimeStep() const { return timeStep_; }
-    /// Return current frame timestep as milliseconds.
-    unsigned GetTimeStepMSec() const { return timeStepMSec_; }
-    /// Return total elapsed time of frames in milliseconds.
-    unsigned GetTotalMSec() const { return totalMSec_; }
-    /// Return current low-resolution timer period in milliseconds.
-    unsigned GetTimerPeriod() const { return timerPeriod_; }
-    
-    /// Sleep for a number of milliseconds.
-    static void Sleep(unsigned mSec);
-    
-private:
-    /// Frame number.
-    unsigned frameNumber_;
-    /// Timestep in seconds.
-    float timeStep_;
-    /// Timestep in milliseconds.
-    unsigned timeStepMSec_;
-    /// Total elapsed time in milliseconds.
-    unsigned totalMSec_;
-    /// Low-resolution timer period.
-    unsigned timerPeriod_;
-};
-
-
 /// Low-resolution operating system timer.
 class Timer
 {
@@ -116,3 +70,44 @@ private:
     /// High-resolution timer frequency.
     static long long frequency;
 };
+
+/// %Time and frame counter subsystem.
+class Time : public Object
+{
+    OBJECT(Time);
+    
+public:
+    /// Construct.
+    Time(Context* context);
+    /// Destruct. Reset the low-resolution timer period if set.
+    virtual ~Time();
+    
+    /// Begin new frame, with (last) frame duration in seconds and send frame start event.
+    void BeginFrame(float timeStep);
+    /// End frame. Increment total time and send frame end event.
+    void EndFrame();
+    /// %Set the low-resolution timer period in milliseconds. 0 resets to the default period.
+    void SetTimerPeriod(unsigned mSec);
+    
+    /// Return frame number, starting from 1 once BeginFrame() is called for the first time.
+    unsigned GetFrameNumber() const { return frameNumber_; }
+    /// Return current frame timestep as seconds.
+    float GetTimeStep() const { return timeStep_; }
+    /// Return current low-resolution timer period in milliseconds.
+    unsigned GetTimerPeriod() const { return timerPeriod_; }
+    /// Return elapsed time from program start as seconds.
+    float GetElapsedTime();
+    
+    /// Sleep for a number of milliseconds.
+    static void Sleep(unsigned mSec);
+    
+private:
+    /// Elapsed time since program start.
+    Timer elapsedTime_;
+    /// Frame number.
+    unsigned frameNumber_;
+    /// Timestep in seconds.
+    float timeStep_;
+    /// Low-resolution timer period.
+    unsigned timerPeriod_;
+};

+ 1 - 2
Engine/Engine/CoreAPI.cpp

@@ -557,8 +557,7 @@ static void RegisterTimer(asIScriptEngine* engine)
     RegisterObject<Time>(engine, "Time");
     engine->RegisterObjectMethod("Time", "uint get_frameNumber() const", asMETHOD(Time, GetFrameNumber), asCALL_THISCALL);
     engine->RegisterObjectMethod("Time", "float get_timeStep() const", asMETHOD(Time, GetTimeStep), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Time", "uint get_timeStepMSec() const", asMETHOD(Time, GetTimeStepMSec), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Time", "uint get_totalMSec() const", asMETHOD(Time, GetTotalMSec), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Time", "float get_elapsedTime()", asMETHOD(Time, GetElapsedTime), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Time@+ get_time()", asFUNCTION(GetTime), asCALL_CDECL);
 }
 

+ 20 - 13
Engine/Engine/Engine.cpp

@@ -55,8 +55,8 @@ Engine::Engine(Context* context) :
     Object(context),
     minFps_(10),
     maxFps_(200),
-    maxInactiveFps_(50),
-    timeStep_(0),
+    maxInactiveFps_(60),
+    timeStep_(0.0f),
     initialized_(false),
     exiting_(false),
     headless_(false)
@@ -176,6 +176,9 @@ bool Engine::Initialize(const String& windowTitle, const String& logName, const
     RegisterObjects();
     RegisterSubsystems();
     
+    // Set maximally accurate low res timer for frame limiting
+    GetSubsystem<Time>()->SetTimerPeriod(1);
+    
     // Start logging
     Log* log = GetSubsystem<Log>();
     log->Open(logName);
@@ -381,7 +384,7 @@ void Engine::Update()
     using namespace Update;
     
     VariantMap eventData;
-    eventData[P_TIMESTEP] = (float)timeStep_ / 1000.f;
+    eventData[P_TIMESTEP] = timeStep_;
     SendEvent(E_UPDATE, eventData);
     
     // Logic post-update event
@@ -416,35 +419,39 @@ void Engine::ApplyFrameLimit()
     if (input && !input->IsActive())
         maxFps = maxInactiveFps_;
     
-    int timeAcc = 0;
+    long long timeAcc = 0;
     
     if (maxFps)
     {
         PROFILE(ApplyFrameLimit);
         
-        int targetMax = 1000 / maxFps;
+        long long targetMax = 1000000LL / maxFps;
+        
         for (;;)
         {
-            timeAcc += frameTimer_.GetMSec(true);
-            if (timeAcc >= targetMax)
+            timeAcc += frameTimer_.GetUSec(true);
+            // Sleep if more than 1 ms off the frame limiting goal
+            if (targetMax - timeAcc > 1000LL)
+            {
+                unsigned sleepTime = (unsigned)((targetMax - timeAcc) / 1000LL);
+                Time::Sleep(sleepTime);
+            }
+            else
                 break;
-            
-            unsigned wait = (targetMax - timeAcc);
-            Time::Sleep(wait / 2);
         }
     }
     else
-        timeAcc = frameTimer_.GetMSec(true);
+        timeAcc = frameTimer_.GetUSec(true);
     
     // If FPS lower than minimum, clamp elapsed time
     if (minFps_)
     {
-        int targetMin = 1000 / minFps_;
+        long long targetMin = 1000000LL / minFps_;
         if (timeAcc > targetMin)
             timeAcc = targetMin;
     }
     
-    timeStep_ = timeAcc;
+    timeStep_ = timeAcc / 1000000.0f;
 }
 
 void Engine::RegisterObjects()

+ 3 - 3
Engine/Engine/Engine.h

@@ -91,9 +91,9 @@ private:
     void RegisterSubsystems();
     
     /// Frame update timer.
-    Timer frameTimer_;
-    /// Next frame timestep in milliseconds.
-    unsigned timeStep_;
+    HiresTimer frameTimer_;
+    /// Next frame timestep in seconds.
+    float timeStep_;
     /// Minimum frames per second.
     unsigned minFps_;
     /// Maximum frames per second.

+ 6 - 6
Engine/Physics/PhysicsWorld.cpp

@@ -219,16 +219,16 @@ void PhysicsWorld::Update(float timeStep)
             // Send accumulated collision events
             SendCollisionEvents();
             
-            // Interpolate transforms of physics objects
-            processedBodies_.Clear();
-            float t = Clamp(timeAcc_ / internalTimeStep, 0.0f, 1.0f);
-            for (PODVector<RigidBody*>::Iterator i = rigidBodies_.Begin(); i != rigidBodies_.End(); ++i)
-                (*i)->PostStep(t, processedBodies_);
-            
             // Send post-step event
             SendEvent(E_PHYSICSPOSTSTEP, eventData);
         }
     }
+    
+    // Interpolate transforms of physics objects
+    processedBodies_.Clear();
+    float t = Clamp(timeAcc_ / internalTimeStep, 0.0f, 1.0f);
+    for (PODVector<RigidBody*>::Iterator i = rigidBodies_.Begin(); i != rigidBodies_.End(); ++i)
+        (*i)->PostStep(t, processedBodies_);
 }
 
 void PhysicsWorld::SetFps(int fps)

+ 0 - 3
Urho3D/Urho3D.cpp

@@ -105,9 +105,6 @@ void Run()
             return;
         }
         
-        // Set 5 ms timer period to allow accurate FPS limiting up to 200 FPS
-        context->GetSubsystem<Time>()->SetTimerPeriod(5);
-        
         // Execute the Start function from the script file, then run the engine loop until exited
         // Hold a shared pointer to the script file to make sure it is not unloaded during runtime
         engine->InitializeScripting();