I've been trying to implement frame-rate independant motion for a simple 2D example (rectangle moving across the screen) written in C++17/SDL 2.0.10 on Windows 10.
My monitor refresh rate is 60Hz and I have my physics update running at 60 updates per second. The problem is that I'm using linear interpolation but my rectangle still jitters/stutters only when there is no fixed update during a frame render and I cannot understand why.
I have:
- Checked for precision issues with floating-point numbers calculating alpha (seems ok)
- Checked possible overflows with nanosecond precision (seems ok)
- Increasing/decreasing physics update rate to be greater/less than frame rate (jittering still present)
- Enabling/disabling VSync (no difference)
- Logged the output of the interpolated drawing position (no abnormalities in position)
Every article I have read says that interpolation is the solution to smooth movement so why does it still jitter? What's wrong in my implementation?
Code example:
#include <cassert>
#include <SDL.h>
#include <chrono>
constexpr int WINDOW_WIDTH = 640 * 2;
constexpr int WINDOW_HEIGHT = 480 * 2;
constexpr int RECT_WIDTH = 128;
constexpr int RECT_HEIGHT = 128;
struct Window
{
unsigned long long lastSecond{};
unsigned long long frames{},lastFrameTime{}, frameTime{};
unsigned long long fixedUpdates{};
unsigned long long accumulator{};
unsigned long long now{};
};
Window window;
SDL_Window* mainWindow = nullptr;
SDL_Renderer* renderer = nullptr;
float cx = 0;
float cy = 64;
float px = cx;
float py = cy;
float rx = 0;
float ry = 0;
float xspeed = 1.0f;
float yspeed = 0;
auto NOW()
{
using namespace std::chrono;
return duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count();
}
void update()
{
// for brevity
}
void drawRectangle(const float x, const float y, uint8_t red = 0, uint8_t green = 0, uint8_t blue = 0)
{
// Draw rect
auto result = SDL_SetRenderDrawColor(renderer, red, green, blue, 0xff);
assert(result == 0);
SDL_FRect fillRect = { x, y, RECT_WIDTH, RECT_HEIGHT };
result = SDL_RenderFillRectF(renderer, &fillRect);
assert(result == 0);
}
void render()
{
window.frames++;
// Clear black
auto result = SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
assert(result == 0);
result = SDL_RenderClear(renderer);
assert(result == 0);
drawRectangle(rx, ry, 0xff);
// Present to screen
SDL_RenderPresent(renderer);
}
void fixedUpdate()
{
window.fixedUpdates++;
px = cx;
py = cy;
cx += xspeed;
cy += yspeed;
}
int main(int argc, char* argv[])
{
#define NS_PER_SECOND (1000000000)
#define TARGET_FPS (60)
#define NS_PER_MS (1000000)
window.lastFrameTime = NOW();
window.lastSecond = NOW();
auto init = SDL_Init(SDL_INIT_VIDEO);
assert(init == 0);
mainWindow = SDL_CreateWindow("Timestep", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL);
assert(mainWindow);
renderer = SDL_CreateRenderer(mainWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
assert(renderer);
SDL_version version;
SDL_GetVersion(&version);
printf("SDL Version: %d.%d.%d\n", version.major, version.patch, version.minor);
while (true)
{
int thisFrameFixedUpdates = 0;
window.now = NOW();
window.frameTime = window.now - window.lastFrameTime;
if (window.frameTime > 25 * NS_PER_MS)
{
window.frameTime = 25 * NS_PER_MS;
}
window.lastFrameTime = window.now;
constexpr unsigned long long dt = (NS_PER_SECOND / TARGET_FPS);
window.accumulator += window.frameTime;
while (window.accumulator >= dt)
{
fixedUpdate();
window.accumulator -= dt;
++thisFrameFixedUpdates;
}
float alpha = window.accumulator / static_cast<float>(dt);
assert(alpha >= 0.0f && alpha <= 1.0f);
rx = cx + ((cx - px) * alpha);
ry = cy + ((cy - py) * alpha);
if (thisFrameFixedUpdates > 0)
{
printf("--- (rx,ry) (%.2f,%.2f)\n",rx, ry);
}
else
{
printf("*** (rx,ry) (%.2f,%.2f)\n",rx, ry);
}
update();
render();
}
return 0;
}
Help much appreciated
Thanks