The ultimate CPU-friendly main loop with fps & frame skipping?

I believe I’ve come up with the best way to handle a main loop - something I’ve noticed a lot of beginners getting stuck with. This works without the need for a vsync, doesn’t throttle the CPU, and includes a FPS counter.

What does everyone think? Can it be improved somehow?

#include <SDL.h>
int main(int argc, char** argv) {
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_Window *w = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);	
	SDL_Renderer *r = SDL_CreateRenderer(w, -1, 0/*SDL_RENDERER_PRESENTVSYNC*/);
	SDL_bool paused = SDL_FALSE, running = SDL_TRUE;
	Uint64 nextFrame = SDL_GetPerformanceCounter(), nextSecond = nextFrame;
	unsigned fps = 0;
	SDL_FRect box = {0, 0, 20, 1080};
	while(running) {
		SDL_Event event;
		while (SDL_PollEvent(&event)) {
			switch (event.type) {
			case SDL_QUIT:
				running = SDL_FALSE;
				paused = SDL_TRUE;
				break;
			case SDL_WINDOWEVENT:
				switch (event.window.event) {
				case SDL_WINDOWEVENT_FOCUS_LOST:
					paused = SDL_TRUE;
					SDL_SetWindowTitle(w, "Paused");
					break;
				case SDL_WINDOWEVENT_FOCUS_GAINED:
					paused = SDL_FALSE;
					nextFrame = SDL_GetPerformanceCounter();
					break;
				}
				break;
			}
		}
		if(!paused && SDL_GetPerformanceCounter() >= nextFrame) {
			int framesSkipped = 0;
			do { /* step game logic */
				if(++box.x == 620)
					box.x = 0;
				nextFrame += SDL_GetPerformanceFrequency() / 60; /* 60 = fps */
			} while (SDL_GetPerformanceCounter() >= nextFrame && framesSkipped++ < 5); /* 5 = maximum frames to skip */
			fps++;
			if (SDL_GetPerformanceCounter() >= nextSecond) {
				static char buf[50];
				SDL_snprintf(buf, sizeof(buf), "%u FPS", fps);
				SDL_SetWindowTitle(w, buf);
				fps = 0;
				nextSecond += SDL_GetPerformanceFrequency();
			}
			SDL_SetRenderDrawColor(r, 0x00, 0x00, 0x00, 0xFF);
			SDL_RenderClear(r);
			SDL_SetRenderDrawColor(r, 0xFF, 0xFF, 0xFF, 0xFF);
			SDL_RenderFillRectF(r, &box);
			SDL_RenderPresent(r);
		} else 
			SDL_Delay(1); /* give back CPU time*/
	}
	SDL_DestroyRenderer(r);
	SDL_DestroyWindow(w);
	SDL_Quit();
	return 0;
}

Why don’t you want to use SDL_RENDERER_PRESENTVSYNC? Your code seems to assume a 60 fps frame-rate, which for example wouldn’t suit my 50 fps Raspberry Pi.

Not all hardware or renderers have a vsync, so it needs to work with or without. The idea of this main loop is to not rely on the vsync to slow the speed of the game down. If your game steps each vsync, then it will run a different speed on different hardware.

1 Like

What’s the advantage in doing it this way rather than doing an SDL_WaitEvent(&event), and setting up an SDL_AddTimer beforehand that fires off callbacks?

SDL_AddTimer doesn’t have enough precision and will cause multithreading problems

Yes, but the technique of ‘stepping’ your game a fixed amount each frame is flawed. Instead you should read the timestamp (e.g. SDL_GetTicks) and use that to determine how far objects should move etc. That way the game runs at the same speed independent of frame rate.

If you update your game state at 60 fps but the renderer is running at 50 fps you will get unpleasant motion artefacts.

What happens if game logic takes longer than one frame to process? on faster CPUs the game logic will behave differently to slower CPUs?

If you’re not fixing your game logic to a consistent amount across different hardware, then game logic will go out of sync on different hardware. The game steps need to be fixed. The display step can calculate the interpolation between game-step frames, so there will be no visual artefacts between a game running at 60hz and a screen displaying at 50hz .

You need to be able to determine the ‘game state’ from the current time only; if the design of your game relies on ‘stepping’ its state you will need to make the steps small enough (say 1ms) to achieve that.

Fundamentally if you want smooth motion you must render your scene at the display rate, and that means you must be able to determine what to render and where at arbitrary time instants. This will cope with different frame rates and the possibility of frames being dropped because the CPU is too slow.

That may be an acceptable approach (depending on the details of the game) but to interpolate you need to know what time instant you are interpolating to, and you can only do that by enabling vsync.

That’s why fixed time step is used to calculate physics, and motion interpolation is used to render the discreet positions between vsync frames. You absolutely need to fix your time step for physics at least, to prevent unpredictable gameplay outcomes at different frame rates. Even in my 2D game, before I fixed the timestep for the game update, my character didn’t jump quite as high at lower frame rates, breaking some of the levels. If you guarantee a fixed step, then the same outcome will happen no matter how many frames got dropped, or how many extra frames got rendered.

Fixed step is independant of frame rate - you just play catch up when less frames got rendered by doing multiple steps, and wait when too many frames are being rendered.

There’s a great article on it here;


And as the article states:

This system is very well known in the game industry and is actually the keystone of games such as Starcraft II or id Software engines !

2 Likes

The game logic is kind out of context here, when talking about the main rendering loop of a graphics library. As @rtrussell mentioned vsync is is about synchronizing with the display refresh rate which is important for at least two reasons:

  1. Avoiding screen tearing (when your system doesn’t hold your hand and sync for you) or choppy motion (when your system does hold your hand and syncs for you with no consideration to your specific needs).
  2. Efficiency, because if you are developing a simple 2D game or emulator, you don’t want it to hog the CPU or GPU rendering itself bajillion times a picosecond, you only want to render as many times as is strictly necessary.

As for the game logic/physics loop there is no one size fits all solution. If you want time correct behavior, than the crucial part is the delta-time calculation. Once you have that you can either make precise calculation based on that time, or if you can’t afford it (either the cognitive or the CPU load) you use the fixed time step trick, to make sure your object don’t move too far and you can skimp on collision detection for instance. That said even this time-correct behavior is not necessary best fit for all types of games. It’s a choice between a choppy game and a slow game. In multiplier setting a slow game is not an option, in tactical games (board games) that aren’t reaction based you might prefer the chop just to save time since the smoothness is not essential, in pretty much every other type of single player game slowdown is preferred. If the target frame rate is 60, but your machine can run the game at something like 55, choppiness can make it unplayable, while the slowdown you wouldn’t even notice, unless you played before at 60.

1 Like