The Basics of Game Loop Implementation – part 2

Intro

In part one, we discussed the basic notion of “game loop” as well as given some code examples. In this 2nd part of the article, we’ll improve on the first examples as follows:

– This time we’ll be using C as the coding language, and the Open GL graphics library. This will allow us to see how to hook-up into the existing loop Open GL exposes, in order to obtain the “mixed step game loop” that was presented in part one.

– We’ll also see how to make the game loop more “energy efficient” by having it use hardware resources in a more optimal fashion (while at the same time not skimp out on game play experience)

– The examples will also introduce some basic notions of how the user input should be handled in the game loop.

About Open GL & Auto-looping game loop

Open GL is a lot more closer to the metal than Java’s Swing library will ever be. One side effect of this is the fact that Open GL is a lot faster. The Java & Swing example discussed in part 1 would run at about 500 graphics updates per second (Frame Per Second aka FPS) on my laptop. A roughly identical version of that example written in C using Open GL, on the same laptop, will now run at about 6000+ FPS:

Granted, the Java example would draw actual images, while this example only draws colored squares. However the game state updates here happen twice as often than in the Java example (50 UPS here, 25 UPS in Java). Given all this we can safely say that Open GL draws stuff a few orders of magnitude faster than Swing. That’s the good side effect. The somewhat bad side effect of Open GL’s closeness to metal (coupled with C’s closeness to metal) is that drawing images needs more coding. And since that would have over-complicated this example without any added bonus to the game loop discussion, I’ve chosen to simply replace the 1-10 counter from the previous example with squares that blink red/green at fixed time intervals.

The actual calls to Open GL are made using the GLUT library. Here’s how the code hooks up to the provided loop. The below snippet is taken from the “autoLoopingGameLoop/ALGameLoop.c” file:


void ALGameLoop_initOpenGL() {
        //...

	glutDisplayFunc(ALGameLoop_onLoop);
	glutIdleFunc(ALGameLoop_onLoop);
	//..
	glutMainLoop();
}

Basically, we need to inform the GLUT library of what function it should call when a graphics rendering update can (is scheduled to) happen – glutDisplayFunc and also what’s the function to call between graphics rendering updates – glutIdleFunc. After this, the call to glutMainLoop() will start the never ending GLUT loop, which will periodically call on a best effort basis the functions declared as callbacks. In this example, we’re passing the same function – ALGameLoop_onLoop for both callbacks. In doing so, we’re able to achieve the same behavior as with the “mixed step game loop” from part 1:


void ALGameLoop_onLoop() {
	double now = glutGet(GLUT_ELAPSED_TIME);
	double timeElapsedMs = ((now-alGameLoopState.lastLoopTime)*1000)/(CLOCKS_PER_SEC);
	alGameLoopState.timeAccumulatedMs += timeElapsedMs;
	ALGameLoop_updateGraphics();

	while(alGameLoopState.timeAccumulatedMs >= DESIRED_STATE_UPDATE_DURATION_MS) {
		ALGameLoop_updateState();
		alGameLoopState.timeAccumulatedMs -= DESIRED_STATE_UPDATE_DURATION_MS;
	}

	alGameLoopState.lastLoopTime = now;
	ALGameLoop_updateMeasurements();
}

This function, which represents the entire recurring step of our game loop, tries to functionally achieve two things: have the game state advance at a fixed step (configured here at 50 game state updates per second aka 50 UPS) and have the graphics rendering updates execute as fast as possible as long as the game state updates frequency holds true.

The implementation in the code snippet above achieves all this by doing the following:

– the first 2 lines at the beginning of the step measure how much time last game loop step took. They add this time interval to a global variable.
– next they’ll call the ALGameLoop_updateGraphics() function, which contains all the OpenGL code for drawing the squares of the appropriate color
– finally, it will check the accumulated loop time that has added up until now, and if one or more game state updates are due, they’ll all be executed now, at which point the necessary (fixed) amount of time which would have normally been allocated to said game state updates is deducted from the accumulated time
– finally the variable containing the time when the current loop step has ended is updated – so that the next loop step can calculate how much this loop step had lasted…

Now, if we look at this game loop in action, we’ll notice the following (as displayed in the windows Title Bar):

– the game state updates (UPS) are fixed at 50 as intended
– the graphics updates (FPS) happen as fast as possible (on my laptop they’re between 6000 and 6500)

Now, because Open GL does all his graphics calculations using the GPU (on your video card), the above numbers translate to the following: The main processor (CPU) consumption is capped at a decent value. The code will only use that fraction of CPU necessary to update the game state 50 times a second and no more. However the GPU will be at almost 100% load, no matter what. Running this app on my laptop, I can see that it uses between 10-17% of the CPU, and about 92% of my GPU! And sure enough if I let it run for a few minutes the fans on my laptop will spin at maximum speed.

I call this the AutoLoopingGameLoop, because part of its looping (namely the graphics update part) is set on “auto”. The code never tries to pace this part of the loop, letting it run as fast as possible. This is not good from a resources consumption point of view. Furthermore it’s useless for the most part of the time: once one of those 50 game state updates per second has executed, you only need on graphics update (before the next game state update happens). Doing multiple graphics updates between two game state updates will simply redraw the same thing and from the human player’s point of view, this makes no difference.

On-demand-looping game loop

This type of game loop still does the game state updates at a fixed time step, but the graphics updates are done at the most once for each game state update. What this means is that after each game state update, we signal to GLUT that we’d like a refresh on the graphics, by calling the glutPostRedisplay(). The nice thing about this function is that is does not en-queue (doesn’t stack up) these calls: if multiple calls to this function have been made, once a re-display actually happens, all those calls are “cleared”. In practice this translates into “frame skipping”: if for some reason the graphic update steps take too long, they will be executed less often in order to ensure that the game update steps run at the desired fixed rate.

Before exploring the code for all this, here’s how you can see it in action with the app:

The “main” function is found in the CGameLoopStudy.c file. If ran with the “odl” command line parameter, it will execute the OnDemanGameLoop demo:

On my laptop, this runs with both a FPS and UPS of 50. Now, we can simulate delays in graphics rendering by tapping the “p” key:

Tapping “p” multiple times will increase the delay in graphics rendering steps execution. This in turn will translate into a decrease of the FPS value, while the UPS value will remain at 50. You can also tap “o” repeatedly to remove the simulated delay incrementally.

On my laptop, the CPU usage for both the AutoLoopingGameLoop and the OnDemandGameLoop is about the same: about 15%. However, here’s how the GPU usage differs between the two:

The above is a screen capture from the GPU-Z program. First the on-demand-looping game loop demo has run. Then, at the time marked by the vertical green line, we switched to the auto-looping game loop. Without detailing too much all of the metrics there, we can tell only by looking at the GPU Load that it changes from about 20-30% to 92%. And since from a functional stand point both versions of the game loop are identical, it’s clearly better to use the on-demand-looping type of game loop.

Now, here’s how this is implemented: first, we now have separate functions for the GLUT idle callback and for the rendering callback:


void ODLGameLoop_initOpenGL() {
	//...
	glutDisplayFunc(ODLGameLoop_updateGraphics);
	glutIdleFunc(ODLGameLoop_onOpenGLIdle);
        //...
	glutMainLoop();
}

Furthermore, now, the ODLGameLoop_onOpenGLIdle function signals to GLUT when it wants to request a graphics update:

void ODLGameLoop_onOpenGLIdle() {
	double now = glutGet(GLUT_ELAPSED_TIME);
	double timeElapsedMs = ((now-odlGameLoopState.lastLoopTime)*1000)/(CLOCKS_PER_SEC);
	odlGameLoopState.timeAccumulatedMs += timeElapsedMs;

	while(odlGameLoopState.timeAccumulatedMs >= DESIRED_STATE_UPDATE_DURATION_MS) {
		ODLGameLoop_updateState();
		odlGameLoopState.timeAccumulatedMs -= DESIRED_STATE_UPDATE_DURATION_MS;

		odlGameLoopState.upsCount++;
		ODLGameLoop_updateMeasurements();

		glutPostRedisplay();
	}

	odlGameLoopState.lastLoopTime = now;
}

Each time a game state update is performed (call to ODLGameLoop_updateState()), we also call glutPostRedisplay().

Finally, it should be mentioned that this example also supports some basic in-game user input handling: by tapping the “w”, “s”, “a”, or “d” keys the blinking squares will move a bit in the respective direction: up, down, left or right. This is achieved by using a queue where user input events are en-queued as they happen, and then popped and processed at the beginning of each game state update:


void ODLGameLoop_onKeyboard(unsigned char key, int x, int y) {
	switch (key) {
	//...
	case 'a': {
		InputManager_pushEvent('L');
		break;
	}
	//...
	}
}


void ODLGameLoop_updateState() {
	ArrowKeyPressedEvent* inputEvent = InputManager_popEvent();
	int i;
	for(i=0; i < odlGameLoopState.gameEntitiesLength; i++) {
		GameEntity_updateState(odlGameLoopState.gameEntities[i], inputEvent);
	}
}

A more detailed discussion and a more involved example on the user input event handling using a queue will be done in a future post.

The 3rd (and final) part of this series will showcase game loop implementation in JavaScript using “requestAnimationFrame”.

The Code Example

The code example for this post can be git-cloned from here:

https://gitlab.com/shivansite/cGameLoopStudy.git

The libraries needed to build are “opengl32” and “glut32”. The code also includes Eclipse project configuration files, so it should work ok if you import it into Eclipse for c/c++.

The main function is found in “CGameLoopStudy.c”. By default it will start the demo for the auto-looping game loop. If the “odl” command line argument is passed, the on-demand-looping game loop demo will start instead.

The code is organised as follows: the “common” folder contains stuff equally shared by each demo: the game entity code (the colored blinking squares), the user input handling code and the various constants. Implementation for the two types of game loops are found in each of the “autoLoopingGameLoop” and “onDemandLoopingGameLoop” folders.

Advertisements
This entry was posted in Game Dev and tagged , , , , , , , , , , . Bookmark the permalink.

3 Responses to The Basics of Game Loop Implementation – part 2

  1. Pingback: The Basics of Game Loop Implementation – part 3 | Shivan's Blog

  2. Pingback: Using a Queue for User Input Evens | Shivan's Blog

  3. Pingback: Using a Queue for User Input Events | Shivan's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s