It's an easy mistake: you write x += 4, the square moves, everything looks fine. On a machine that manages 60 frames per second that's 240 pixels per second. At 30 frames it's only 120, and the game feels like slow motion.
The orange square at the top does exactly that, per frame. A click in the demo switches the frame rate to 15, 30, 60 and 144, and the orange square gets slower or faster with it. The teal one below always keeps the same speed.
Pixels per second times seconds
Instead of pixels per frame you work in pixels per second and multiply by the time that has passed since the last frame. That's delta time:
const Uint64 now = SDL_GetPerformanceCounter(); const double delta = double(now - last) / SDL_GetPerformanceFrequency(); last = now; x += 240.0f * float(delta);
At 60 frames delta is about 0.0167 seconds, so the square moves 4 pixels per frame. At 30 frames it's 0.033 seconds and 8 pixels per frame. Per second it covers 240 pixels either way.
Catching big jumps
If you switch tabs or the game stalls for a moment, delta is suddenly two seconds long and the square leaps across the screen, in the worst case through a wall. The demo clamps the time to a quarter of a second:
const double delta = std::min(sinceFrame, 0.25);
That's enough for a square. Once physics and collisions come in, the result still depends on the frame rate, even if everything feels similar. Then you want a fixed timestep, like the one in the update loop demo.
While building the demo I introduced a bug of my own. At a target of 60 it showed only 40 frames per second. I had compared the interval exactly, and the display delivers frames after 16.6 milliseconds one time and 16.7 the next. Every second frame just missed. Now a frame counts even if it is two milliseconds early.
The full code is in the repo under delta-time. The project Game Dev Snippets collects all demos of the series.