Sleep between frames to save CPU cycles

This commit is contained in:
GoaLitiuM
2021-05-29 20:15:29 +03:00
parent cc60814334
commit e06200926f
3 changed files with 62 additions and 50 deletions

View File

@@ -146,26 +146,22 @@ int32 Engine::Main(const Char* cmdLine)
EngineImpl::IsReady = true;
// Main engine loop
bool canDraw = true; // Prevent drawing 2 or more frames in a row without update or fixed update (nothing will change)
const bool useSleep = true; // TODO: this should probably be a platform setting
while (!ShouldExit())
{
#if 0
// TODO: test it more and maybe use in future to reduce CPU usage
// Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend
{
float tickFps;
auto tick = Time->GetHighestFrequency(tickFps);
double tickTargetStepTime = 1.0 / tickFps;
double nextTick = tick->LastEnd + tickTargetStepTime;
double timeToTick = nextTick - Platform::GetTimeSeconds();
int32 sleepTimeMs = Math::Min(4, Math::FloorToInt(timeToTick * (1000.0 * 0.8))); // Convert seconds to milliseconds and apply adjustment with limit
if (!Device->WasVSyncUsed() && sleepTimeMs > 0)
{
PROFILE_CPU_NAMED("Idle");
Platform::Sleep(sleepTimeMs);
}
}
#endif
// Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend
if ((useSleep && Time::UpdateFPS > 0) || !Platform::GetHasFocus())
{
double nextTick = Time::GetNextTick();
double timeToTick = nextTick - Platform::GetTimeSeconds();
// Sleep less than needed, some platforms may sleep slightly more than requested
if (timeToTick > 0.002)
{
PROFILE_CPU_NAMED("Idle");
Platform::Sleep(1);
}
}
// App paused logic
if (Platform::GetIsPaused())
@@ -187,7 +183,6 @@ int32 Engine::Main(const Char* cmdLine)
OnUpdate();
OnLateUpdate();
Time::OnEndUpdate();
canDraw = true;
}
// Start physics simulation
@@ -195,16 +190,14 @@ int32 Engine::Main(const Char* cmdLine)
{
OnFixedUpdate();
Time::OnEndPhysics();
canDraw = true;
}
// Draw frame
if (canDraw && Time::OnBeginDraw())
if (Time::OnBeginDraw())
{
OnDraw();
Time::OnEndDraw();
FrameMark;
canDraw = false;
}
// Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)

View File

@@ -70,6 +70,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
}
void Time::TickData::OnReset(float targetFps, double currentTime)
@@ -91,10 +92,18 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime)
}
else
{
deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime);
const double minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0;
if (deltaTime < minDeltaTime)
if (time < NextBegin)
return false;
deltaTime = Math::Max((time - LastBegin), 0.0);
if (deltaTime > maxDeltaTime)
{
deltaTime = (double)maxDeltaTime;
NextBegin = time;
}
if (targetFps > ZeroTolerance)
NextBegin += (1.0 / targetFps);
}
// Update data
@@ -135,10 +144,19 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
}
else
{
deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime);
minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0;
if (deltaTime < minDeltaTime)
if (time < NextBegin)
return false;
minDeltaTime = targetFps > ZeroTolerance ? 1.0 / targetFps : 0.0;
deltaTime = Math::Max((time - LastBegin), 0.0);
if (deltaTime > maxDeltaTime)
{
deltaTime = (double)maxDeltaTime;
NextBegin = time;
}
if (targetFps > ZeroTolerance)
NextBegin += (1.0 / targetFps);
}
Samples.Add(deltaTime);
@@ -158,26 +176,23 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime)
return true;
}
Time::TickData* Time::GetHighestFrequency(float& fps)
double Time::GetNextTick()
{
const auto updateFps = UpdateFPS;
const auto drawFps = DrawFPS;
const auto physicsFps = PhysicsFPS;
const double nextUpdate = Time::Update.NextBegin;
const double nextPhysics = Time::Physics.NextBegin;
const double nextDraw = Time::Draw.NextBegin;
if (physicsFps >= drawFps && physicsFps >= updateFps)
{
fps = physicsFps;
return &Physics;
}
double nextTick = MAX_double;
if (UpdateFPS > 0 && nextUpdate < nextTick)
nextTick = nextUpdate;
if (PhysicsFPS > 0 && nextPhysics < nextTick)
nextTick = nextPhysics;
if (DrawFPS > 0 && nextDraw < nextTick)
nextTick = nextDraw;
if (drawFps >= physicsFps && drawFps >= updateFps)
{
fps = drawFps;
return &Draw;
}
fps = updateFps;
return &Update;
if (nextTick == MAX_double)
return 0.0;
return nextTick;
}
void Time::SetGamePaused(bool value)

View File

@@ -48,6 +48,11 @@ public:
/// </summary>
double LastLength;
/// <summary>
/// The next tick start time.
/// </summary>
double NextBegin;
/// <summary>
/// The delta time.
/// </summary>
@@ -167,11 +172,10 @@ public:
}
/// <summary>
/// Gets the tick data that uses the highest frequency for the ticking.
/// Gets the time of next upcoming tick data of ticking group with defined update frequency.
/// </summary>
/// <param name="fps">The FPS rate of the highest frequency ticking group.</param>
/// <returns>The tick data.</returns>
static TickData* GetHighestFrequency(float& fps);
/// <returns>The time of next tick.</returns>
static double GetNextTick();
/// <summary>
/// Gets the value indicating whenever game logic is paused (physics, script updates, etc.).
@@ -185,7 +189,7 @@ public:
/// <summary>
/// Sets the value indicating whenever game logic is paused (physics, script updates, etc.).
/// </summary>
/// <param name="value">>True if pause game logic, otherwise false.</param>
/// <param name="value">True if pause game logic, otherwise false.</param>
API_PROPERTY() static void SetGamePaused(bool value);
/// <summary>