Added hybrid sleep/spin wait mode.

This commit is contained in:
Muzychenko Andrey 2021-11-18 17:58:53 +03:00
parent 545af17b3b
commit 2229f9b70e
4 changed files with 73 additions and 3 deletions

View File

@ -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);
} }

View File

@ -73,6 +73,7 @@ struct optionsStruct
bool ShowMenu; bool ShowMenu;
bool UncappedUpdatesPerSecond; bool UncappedUpdatesPerSecond;
int SoundChannels; int SoundChannels;
bool HybridSleep;
}; };
struct ControlRef struct ControlRef

View File

@ -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;);
}

View File

@ -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);
}; };