5
\$\begingroup\$

I've been reading Koen Witters detailed article about different game loop solutions but I'm having some problems implementing the last one with GLUT, which is the recommended one.

After reading a couple of articles, tutorials and code from other people on how to achieve a constant game speed, I think that what I currently have implemented (I'll post the code below) is what Koen Witters called Game Speed dependent on Variable FPS, the second on his article.

First, through my searching experience, there's a couple of people that probably have the knowledge to help out on this but don't know what GLUT is and I'm going to try and explain (feel free to correct me) the relevant functions for my problem of this OpenGL toolkit. Skip this section if you know what GLUT is and how to play with it.

GLUT Toolkit:

  • GLUT is an OpenGL toolkit and helps with common tasks in OpenGL.
  • The glutDisplayFunc(renderScene) takes a pointer to a renderScene() function callback, which will be responsible for rendering everything. The renderScene() function will only be called once after the callback registration.
  • The glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0) takes the number of milliseconds to pass before calling the callback processAnimationTimer(). The last argument is just a value to pass to the timer callback. The processAnimationTimer() will not be called each TIMER_MILLISECONDS but just once.
  • The glutPostRedisplay() function requests GLUT to render a new frame so we need call this every time we change something in the scene.
  • The glutIdleFunc(renderScene) could be used to register a callback to renderScene() (this does not make glutDisplayFunc() irrelevant) but this function should be avoided because the idle callback is continuously called when events are not being received, increasing the CPU load.
  • The glutGet(GLUT_ELAPSED_TIME) function returns the number of milliseconds since glutInit was called (or first call to glutGet(GLUT_ELAPSED_TIME)). That's the timer we have with GLUT. I know there are better alternatives for high resolution timers, but let's keep with this one for now.

I think this is enough information on how GLUT renders frames so people that didn't know about it could also pitch in this question to try and help if they fell like it.

Current Implementation:

Now, I'm not sure I have correctly implemented the second solution proposed by Koen, Game Speed dependent on Variable FPS. The relevant code for that goes like this:

#define TICKS_PER_SECOND 30
#define MOVEMENT_SPEED 2.0f

const int TIMER_MILLISECONDS = 1000 / TICKS_PER_SECOND;

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // Do all drawing below...

    (...)
}

void processAnimationTimer(int value) {
    // setups the timer to be called again
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Requests to render a new frame (this will call my renderScene() once)
    glutPostRedisplay();
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    // Setup the timer to be called one first time
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);
    // Read the current time since glutInit was called
    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

This implementation doesn't fell right. It works in the sense that helps the game speed to be constant dependent on the FPS. So that moving from point A to point B takes the same time no matter the high/low framerate. However, I believe I'm limiting the game framerate with this approach. Each frame will only be rendered when the time callback is called, that means the framerate will be roughly around TICKS_PER_SECOND frames per second. This doesn't feel right, you shouldn't limit your powerful hardware, it's wrong. It's my understanding though, that I still need to calculate the elapsedTime. Just because I'm telling GLUT to call the timer callback every TIMER_MILLISECONDS, it doesn't mean it will always do that on time.

I'm not sure how can I fix this and to be completely honest, I have no idea what is the game loop in GLUT, you know, the while( game_is_running ) loop in Koen's article. But it's my understanding that GLUT is event-driven and that game loop starts when I call glutMainLoop() (which never returns), yes?

I thought I could register an idle callback with glutIdleFunc() and use that as replacement of glutTimerFunc(), only rendering when necessary (instead of all the time as usual) but when I tested this with an empty callback (like void gameLoop() {}) and it was basically doing nothing, only a black screen, the CPU spiked to 25% and remained there until I killed the game and it went back to normal. So I don't think that's the path to follow.

Using glutTimerFunc() is definitely not a good approach to perform all movements/animations based on that, as I'm limiting my game to a constant FPS, not cool. Or maybe I'm using it wrong and my implementation is not right?

How exactly can I have a constant game speed with variable FPS? More exactly, how do I correctly implement Koen's Constant Game Speed with Maximum FPS solution (the fourth one on his article) with GLUT? Maybe this is not possible at all with GLUT? If not, what are my alternatives? What is the best approach to this problem (constant game speed) with GLUT?

I originally posted this question on Stack Overflow before being pointed out about this site. The following is a different approach I tried after creating the question in SO, so I'm posting it here too.

Another Approach:

I've been experimenting and here's what I was able to achieve now. Instead of calculating the elapsed time on a timed function (which limits my game's framerate) I'm now doing it in renderScene(). Whenever changes to the scene happen I call glutPostRedisplay() (ie: camera moving, some object animation, etc...) which will make a call to renderScene(). I can use the elapsed time in this function to move my camera for instance.

My code has now turned into this:

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // All drawing code goes inside this function
    drawCompleteScene();

    glutSwapBuffers();

    /* Redraw the frame ONLY if the user is moving the camera
       (similar code will be needed to redraw the frame for other events) */
    if(!IsTupleEmpty(cameraDirection)) {
        glutPostRedisplay();
    }
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

Conclusion, it's working, or so it seems. If I don't move the camera, the CPU usage is low, nothing is being rendered (for testing purposes I only have a grid extending for 4000.0f, while zFar is set to 1000.0f). When I start moving the camera the scene starts redrawing itself. If I keep pressing the move keys, the CPU usage will increase; this is normal behavior. It drops back when I stop moving.

Unless I'm missing something, it seems like a good approach for now. I did find this interesting article on iDevGames and this implementation is probably affected by the problem described on that article. What's your thoughts on that?

Please note that I'm just doing this for fun, I have no intentions of creating some game to distribute or something like that, not in the near future at least. If I did, I would probably go with something else besides GLUT. But since I'm using GLUT, and other than the problem described on iDevGames, do you think this latest implementation is sufficient for GLUT? The only real issue I can think of right now is that I'll need to keep calling glutPostRedisplay() every time the scene changes something and keep calling it until there's nothing new to redraw. A little complexity added to the code for a better cause, I think.

What do you think?

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

I haven't read those articles you linked, but whenever I want to implement constant game speed, I call elapsedTime dependent code in my render function. Just like you in your second solution. And I think it's really sufficient for this purpose.

\$\endgroup\$
1
  • \$\begingroup\$ I'm marking this one as accepted because it was straight to the point and I'm actually going with my second solution, it's working fine for now. I believe it suffices for the time being. \$\endgroup\$
    – rfgamaral
    Mar 21, 2011 at 20:08
8
\$\begingroup\$

First off, you shouldn't be using GLUT for anything but testing. As far as I know, GLUT was never meant for application development. I recommend picking up SDL or SFML instead.

That said, implementing framerate-independent rendering is relatively simple.

// global gLastTick contains the last processed tick (ms)

// game loop
int tick = getCurrentTickSomeWay();
if (tick < gLastTick)
{
  // Going too fast
  sleep(1); // or however sleep is implemented in your framework
  return;   // go another round
}
// Here's how the magic is done
while (tick > gLastTick)
{
  // Update game physics
  update();
  gLastTick += 1000 / DESIRED_PHYSICS_FPS;
}
// Now we're ready to render
render();
// and swap
swap(); // again, however that's done in your framework

Caveats:

  1. If your game does not get updates for a while for some reason (break in debugger, machine goes to sleep, etc), delta between tick and gLastTick can grow rather large, so adding a safeguard "if (tick - gLastTick > 1000) gLastTick = tick;" may be useful.

  2. If update() takes about as much or more time than (1000 / DESIRED_PHYSICS_FPS) ms, the game will grind to a halt. This is mostly relevant for heavy / complex physics cases (like trying to collide 10000 objects. Yes, I tried. It's heavy).

  3. User input is still dependent on framerate, so if framerate drops below ~20fps, depending on your game, the game may become difficult to control.

\$\endgroup\$
4
  • \$\begingroup\$ Your post doesn't give me much more than what I already had seen on both of the articles I posted. I still have the same questions when I first started this topic, how to do that in GLUT?. That's what I don't understand because its event-driven behavior. \$\endgroup\$
    – rfgamaral
    Mar 19, 2011 at 13:58
  • \$\begingroup\$ Step 1: add a glut idle func that calls glutPostRedisplay. Step 2: do the stuff I mentioned in my post in your glut display func. This was based on a quick read of the GLUT 3 spec ( cs.arizona.edu/classes/cs433/spring02/opengl/docs/… ), but I seriously recommend moving to SDL or SFML instead. \$\endgroup\$ Mar 19, 2011 at 21:27
  • \$\begingroup\$ I've already explained in the question post why I don't want to use GLUT idle function. \$\endgroup\$
    – rfgamaral
    Mar 20, 2011 at 2:13
  • \$\begingroup\$ Then sleep more. Or use a timer function to trigger the refresh instead. The glut spec specifically says that you should refresh from idle in order to get continious rendering. It won't get called more than once per render cycle anyway, as you won't be idle. \$\endgroup\$ Mar 20, 2011 at 10:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .