mirror of
https://github.com/seekrs/MacroLibX.git
synced 2026-09-03 01:20:00 +02:00
Fix inconsistencies with the FPS scheduler (#241)
The current Fps manager will sometime skip the intended delay and start the next frame immediately, I'm not 100% sure why it happens, but I rewrote most of it to stay consistent and improved the overall precision by compensating for OS wake delay after sleep and finishing with a busy wait I also changed the behavior of mlx_set_fps_goal to allow zero/negative values and treat them as uncapped (like most graphics libraries)
This commit is contained in:
@@ -1,26 +1,30 @@
|
|||||||
#ifndef __MLX_FPS__
|
#ifndef __MLX_FPS__
|
||||||
#define __MLX_FPS__
|
#define __MLX_FPS__
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
namespace mlx
|
namespace mlx
|
||||||
{
|
{
|
||||||
|
typedef std::chrono::steady_clock fps_clock;
|
||||||
|
|
||||||
class FpsManager
|
class FpsManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FpsManager() = default;
|
FpsManager() = default;
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
bool Update();
|
void WaitUntilNextFrame();
|
||||||
inline void SetMaxFPS(std::uint32_t fps) noexcept { m_max_fps = fps; m_ns = 1000000000.0 / fps; }
|
inline void SetMaxFPS(std::uint32_t fps) noexcept { m_target_delta = fps_clock::duration(std::chrono::seconds(1)) / fps;}
|
||||||
|
|
||||||
~FpsManager() = default;
|
~FpsManager() = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double m_ns = 1000000000.0 / 1'337'000.0;
|
fps_clock::time_point m_current_time;
|
||||||
std::int64_t m_fps_before = 0;
|
fps_clock::time_point m_target_time;
|
||||||
std::int64_t m_fps_now = 0;
|
fps_clock::time_point m_last_time_record;
|
||||||
std::int64_t m_timer = 0;
|
fps_clock::duration m_delta_time = fps_clock::duration().zero();
|
||||||
std::uint32_t m_max_fps = 1'337'000;
|
fps_clock::duration m_target_delta = fps_clock::duration().zero();
|
||||||
std::uint32_t m_fps_elapsed_time = 0;
|
fps_clock::duration m_sleep_margin = std::chrono::microseconds(200);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ namespace mlx
|
|||||||
|
|
||||||
while(m_in.IsRunning())
|
while(m_in.IsRunning())
|
||||||
{
|
{
|
||||||
if(!m_fps.Update())
|
m_fps.WaitUntilNextFrame();
|
||||||
continue;
|
|
||||||
|
|
||||||
m_in.FetchInputs();
|
m_in.FetchInputs();
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,9 @@ extern "C"
|
|||||||
void mlx_set_fps_goal(mlx_context mlx, int fps)
|
void mlx_set_fps_goal(mlx_context mlx, int fps)
|
||||||
{
|
{
|
||||||
MLX_CHECK_APPLICATION_POINTER(mlx);
|
MLX_CHECK_APPLICATION_POINTER(mlx);
|
||||||
if(fps < 0)
|
if(fps <= 0)
|
||||||
mlx::Error("You cannot set a negative FPS cap (nice try)");
|
fps = -1;
|
||||||
else
|
mlx->app->SetFPSCap(static_cast<std::uint32_t>(fps));
|
||||||
mlx->app->SetFPSCap(static_cast<std::uint32_t>(fps));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mlx_destroy_context(mlx_context mlx)
|
void mlx_destroy_context(mlx_context mlx)
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
#include <PreCompiled.h>
|
#include <PreCompiled.h>
|
||||||
#include <Core/Fps.h>
|
#include <Core/Fps.h>
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace mlx
|
namespace mlx
|
||||||
{
|
{
|
||||||
void FpsManager::Init()
|
void FpsManager::Init()
|
||||||
{
|
{
|
||||||
m_timer = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
|
m_current_time = fps_clock::now();
|
||||||
m_fps_before = m_timer;
|
m_target_time = m_current_time + m_target_delta;
|
||||||
m_fps_now = m_timer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FpsManager::Update()
|
void FpsManager::WaitUntilNextFrame()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
m_current_time = fps_clock::now();
|
||||||
m_fps_now = static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count());
|
if(m_current_time < m_target_time)
|
||||||
|
|
||||||
if(std::chrono::duration<std::uint64_t>{m_fps_now - m_timer} >= 1s)
|
|
||||||
m_timer += m_fps_now;
|
|
||||||
|
|
||||||
m_fps_elapsed_time = m_fps_now - m_fps_before;
|
|
||||||
if(m_fps_elapsed_time >= m_ns)
|
|
||||||
{
|
{
|
||||||
m_fps_before += m_ns;
|
std::this_thread::sleep_until(m_target_time - m_sleep_margin);
|
||||||
return true;
|
m_current_time = fps_clock::now();
|
||||||
|
while (m_current_time < m_target_time)
|
||||||
|
{
|
||||||
|
#ifndef __APPLE__
|
||||||
|
_mm_pause(); // reduces CPU usage on x86 without yielding
|
||||||
|
#endif
|
||||||
|
m_current_time = fps_clock::now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::duration<double, std::nano>(m_ns - 1));
|
else if (m_target_time < m_current_time - m_target_delta * 4)
|
||||||
return false;
|
m_target_time = m_current_time;
|
||||||
|
m_target_time += m_target_delta;
|
||||||
|
m_delta_time = m_current_time - m_last_time_record;
|
||||||
|
m_last_time_record = m_current_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user