mirror of
https://github.com/k4zmu2a/SpaceCadetPinball.git
synced 2024-11-25 12:12:32 +01:00
Added hybrid sleep/spin wait mode.
This commit is contained in:
parent
545af17b3b
commit
2229f9b70e
@ -99,6 +99,7 @@ void options::init()
|
|||||||
Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false);
|
Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false);
|
||||||
Options.SoundChannels = get_int("Sound Channels", DefSoundChannels);
|
Options.SoundChannels = get_int("Sound Channels", DefSoundChannels);
|
||||||
Options.SoundChannels = std::min(MaxSoundChannels, std::max(MinSoundChannels, Options.SoundChannels));
|
Options.SoundChannels = std::min(MaxSoundChannels, std::max(MinSoundChannels, Options.SoundChannels));
|
||||||
|
Options.HybridSleep = get_int("HybridSleep", false);
|
||||||
|
|
||||||
winmain::UpdateFrameRate();
|
winmain::UpdateFrameRate();
|
||||||
|
|
||||||
@ -130,6 +131,7 @@ void options::uninit()
|
|||||||
set_int("ShowMenu", Options.ShowMenu);
|
set_int("ShowMenu", Options.ShowMenu);
|
||||||
set_int("Uncapped Updates Per Second", Options.UncappedUpdatesPerSecond);
|
set_int("Uncapped Updates Per Second", Options.UncappedUpdatesPerSecond);
|
||||||
set_int("Sound Channels", Options.SoundChannels);
|
set_int("Sound Channels", Options.SoundChannels);
|
||||||
|
set_int("HybridSleep", Options.HybridSleep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ struct optionsStruct
|
|||||||
bool ShowMenu;
|
bool ShowMenu;
|
||||||
bool UncappedUpdatesPerSecond;
|
bool UncappedUpdatesPerSecond;
|
||||||
int SoundChannels;
|
int SoundChannels;
|
||||||
|
bool HybridSleep;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ControlRef
|
struct ControlRef
|
||||||
|
@ -42,6 +42,8 @@ std::string winmain::FpsDetails;
|
|||||||
double winmain::UpdateToFrameRatio;
|
double winmain::UpdateToFrameRatio;
|
||||||
winmain::DurationMs winmain::TargetFrameTime;
|
winmain::DurationMs winmain::TargetFrameTime;
|
||||||
optionsStruct& winmain::Options = options::Options;
|
optionsStruct& winmain::Options = options::Options;
|
||||||
|
winmain::DurationMs winmain::SpinThreshold = DurationMs(0.005);
|
||||||
|
WelfordState winmain::SleepState{};
|
||||||
|
|
||||||
int winmain::WinMain(LPCSTR lpCmdLine)
|
int winmain::WinMain(LPCSTR lpCmdLine)
|
||||||
{
|
{
|
||||||
@ -115,7 +117,8 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
|||||||
|
|
||||||
// If HW fails, fallback to SW SDL renderer.
|
// If HW fails, fallback to SW SDL renderer.
|
||||||
SDL_Renderer* renderer = nullptr;
|
SDL_Renderer* renderer = nullptr;
|
||||||
for (int i = 0; i < 2 && !renderer; i++)
|
auto swOffset = strstr(lpCmdLine, "-sw") != nullptr ? 1 : 0;
|
||||||
|
for (int i = swOffset; i < 2 && !renderer; i++)
|
||||||
{
|
{
|
||||||
Renderer = renderer = SDL_CreateRenderer
|
Renderer = renderer = SDL_CreateRenderer
|
||||||
(
|
(
|
||||||
@ -288,7 +291,10 @@ int winmain::WinMain(LPCSTR lpCmdLine)
|
|||||||
TimePoint frameEnd;
|
TimePoint frameEnd;
|
||||||
if (targetTimeDelta > DurationMs::zero() && !Options.UncappedUpdatesPerSecond)
|
if (targetTimeDelta > DurationMs::zero() && !Options.UncappedUpdatesPerSecond)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(targetTimeDelta);
|
if (Options.HybridSleep)
|
||||||
|
HybridSleep(targetTimeDelta);
|
||||||
|
else
|
||||||
|
std::this_thread::sleep_for(targetTimeDelta);
|
||||||
frameEnd = Clock::now();
|
frameEnd = Clock::now();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -519,6 +525,12 @@ void winmain::RenderUi()
|
|||||||
{
|
{
|
||||||
Options.UncappedUpdatesPerSecond ^= true;
|
Options.UncappedUpdatesPerSecond ^= true;
|
||||||
}
|
}
|
||||||
|
if (ImGui::MenuItem("Precise Sleep", nullptr, Options.HybridSleep))
|
||||||
|
{
|
||||||
|
Options.HybridSleep ^= true;
|
||||||
|
SleepState = WelfordState{};
|
||||||
|
SpinThreshold = DurationMs::zero();
|
||||||
|
}
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
@ -947,7 +959,9 @@ void winmain::RenderFrameTimeDialog()
|
|||||||
auto target = static_cast<float>(TargetFrameTime.count());
|
auto target = static_cast<float>(TargetFrameTime.count());
|
||||||
auto scale = 1 / (64 / 2 / target);
|
auto scale = 1 / (64 / 2 / target);
|
||||||
|
|
||||||
ImGui::Text("Target frame time:%03.04fms, 1px:%03.04fms", target, scale);
|
auto spin = Options.HybridSleep ? static_cast<float>(SpinThreshold.count()) : 0;
|
||||||
|
ImGui::Text("Target frame time:%03.04fms, 1px:%03.04fms, SpinThreshold:%03.04fms",
|
||||||
|
target, scale, spin);
|
||||||
gfr_display->BlitToTexture();
|
gfr_display->BlitToTexture();
|
||||||
auto region = ImGui::GetContentRegionAvail();
|
auto region = ImGui::GetContentRegionAvail();
|
||||||
ImGui::Image(gfr_display->Texture, region);
|
ImGui::Image(gfr_display->Texture, region);
|
||||||
@ -955,3 +969,28 @@ void winmain::RenderFrameTimeDialog()
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void winmain::HybridSleep(DurationMs sleepTarget)
|
||||||
|
{
|
||||||
|
static constexpr double StdDevFactor = 0.5;
|
||||||
|
|
||||||
|
// This nice concept is from https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
|
||||||
|
// Sacrifices some CPU time for smaller frame time jitter
|
||||||
|
while (sleepTarget > SpinThreshold)
|
||||||
|
{
|
||||||
|
auto start = Clock::now();
|
||||||
|
std::this_thread::sleep_for(DurationMs(1));
|
||||||
|
auto end = Clock::now();
|
||||||
|
|
||||||
|
auto actualDuration = DurationMs(end - start);
|
||||||
|
sleepTarget -= actualDuration;
|
||||||
|
|
||||||
|
// Update expected sleep duration using Welford's online algorithm
|
||||||
|
// With bad timer, this will run away to 100% spin
|
||||||
|
SleepState.Advance(actualDuration.count());
|
||||||
|
SpinThreshold = DurationMs(SleepState.mean + SleepState.GetStdDev() * StdDevFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// spin lock
|
||||||
|
for (auto start = Clock::now(); DurationMs(Clock::now() - start) < sleepTarget;);
|
||||||
|
}
|
||||||
|
@ -33,6 +33,31 @@ struct SdlPerformanceClock
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WelfordState
|
||||||
|
{
|
||||||
|
double mean;
|
||||||
|
double M2;
|
||||||
|
int64_t count;
|
||||||
|
|
||||||
|
WelfordState() : mean(0.005), M2(0), count(1)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Advance(double newValue)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
auto delta = newValue - mean;
|
||||||
|
mean += delta / count;
|
||||||
|
M2 += delta * (newValue - mean); //M2n = M2n-1 + (Xn - AvgXn-1) * (Xn - AvgXn)
|
||||||
|
}
|
||||||
|
|
||||||
|
double GetStdDev() const
|
||||||
|
{
|
||||||
|
return std::sqrt(M2 / (count - 1)); // Sn^2 = M2n / (n - 1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class winmain
|
class winmain
|
||||||
{
|
{
|
||||||
using Clock = SdlPerformanceClock; // Or std::chrono::steady_clock.
|
using Clock = SdlPerformanceClock; // Or std::chrono::steady_clock.
|
||||||
@ -75,7 +100,10 @@ private:
|
|||||||
static double UpdateToFrameRatio;
|
static double UpdateToFrameRatio;
|
||||||
static DurationMs TargetFrameTime;
|
static DurationMs TargetFrameTime;
|
||||||
static struct optionsStruct& Options;
|
static struct optionsStruct& Options;
|
||||||
|
static DurationMs SpinThreshold;
|
||||||
|
static WelfordState SleepState;
|
||||||
|
|
||||||
static void RenderUi();
|
static void RenderUi();
|
||||||
static void RenderFrameTimeDialog();
|
static void RenderFrameTimeDialog();
|
||||||
|
static void HybridSleep(DurationMs seconds);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user