Skip to content

Instantly share code, notes, and snippets.

@ripwin
Created December 22, 2024 08:24
Show Gist options
  • Save ripwin/8d10a7af1ccd2f51cdce53594cda5d26 to your computer and use it in GitHub Desktop.
Save ripwin/8d10a7af1ccd2f51cdce53594cda5d26 to your computer and use it in GitHub Desktop.
Stuttering
// Stating point
/* Utilizes lockstep rendering.
* A big thanks to Tyler Glaiel for the sample code.
* https://github.com/TylerGlaiel/FrameTimingControl
* */
void Application::run(std::unique_ptr<IGame> game)
{
m_game = std::move(game);
auto const fixedStepTarget{
std::chrono::nanoseconds{static_cast<int64_t>(1.0 / 60.0 * 1e9)}};
auto const vSyncMaxError{
std::chrono::nanoseconds{static_cast<int64_t>(0.0001 * 1e9)}};
auto const windowRefreshRate{m_window->refreshRate()};
auto const refreshRate{windowRefreshRate.has_value()
? windowRefreshRate.value()
: 60.0};
// VSync multiples to snap delta time to
auto snapFrequencies{std::vector<std::chrono::nanoseconds>(8)};
for (auto i{std::size_t{}}; i < snapFrequencies.size(); ++i)
{
snapFrequencies[i] = std::chrono::nanoseconds{
static_cast<int64_t>(static_cast<double>(i + 1) / refreshRate * 1e9)};
}
// DeltaTime histories to calculate the average
auto const theoreticalDeltaTime{std::chrono::nanoseconds{
static_cast<int64_t>(1.0 / refreshRate * 1e9)}};
auto deltaTimeHistories{std::vector<std::chrono::nanoseconds>(4, theoreticalDeltaTime)};
auto deltaTimeAverageResidual{std::chrono::nanoseconds{}};
auto currentTime{std::chrono::steady_clock::now()};
auto lastTime{std::chrono::steady_clock::now()};
auto deltaTime{std::chrono::nanoseconds{0}};
auto accumulator{std::chrono::nanoseconds{0}};
while (!m_window->isClosing())
{
currentTime = std::chrono::steady_clock::now();
deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Cap unexpected time anomalies
if (deltaTime > theoreticalDeltaTime * 8)
{
deltaTime = theoreticalDeltaTime;
}
else if (deltaTime < std::chrono::nanoseconds{0})
{
deltaTime = std::chrono::nanoseconds{0};
}
// Snap to VSync
for (auto const& snap : snapFrequencies)
{
auto difference{deltaTime - snap};
if (difference < std::chrono::nanoseconds{})
{
difference = -difference;
}
if (difference < vSyncMaxError)
{
deltaTime = snap;
break;
}
}
// DeltaTime history (shift and update)
for (auto i{std::size_t{}}; i < deltaTimeHistories.size() - 1; ++i)
{
deltaTimeHistories[i] = deltaTimeHistories[i + 1];
}
deltaTimeHistories.back() = deltaTime;
// DeltaTime average
auto totalDeltaTimeHistories{std::chrono::nanoseconds{0}};
for (auto const& history : deltaTimeHistories)
{
totalDeltaTimeHistories += history;
}
deltaTime = totalDeltaTimeHistories / deltaTimeHistories.size();
deltaTimeAverageResidual += totalDeltaTimeHistories % deltaTimeHistories.size();
deltaTime += deltaTimeAverageResidual / deltaTimeHistories.size();
deltaTimeAverageResidual = deltaTimeAverageResidual % deltaTimeHistories.size();
accumulator += deltaTime;
// Spiral of death protection
auto const maxAccumulator = theoreticalDeltaTime * 8;
if (accumulator > maxAccumulator)
{
accumulator = maxAccumulator;
}
m_window->pollEvent();
if (!m_window->isClosing())
{
// Update
while (accumulator >= fixedStepTarget)
{
m_game->onUpdate(
std::chrono::duration_cast<std::chrono::duration<float>>(fixedStepTarget).count());
accumulator -= fixedStepTarget;
}
m_game->onRender();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment