Skip to Content
Course content

152: Setting Up a Basic Rendering Loop

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember a junior developer I mentored a few years back who was building his first physics-based platformer. He had the movement logic working perfectly on his high-end gaming rig, but when he sent the build to the QA lead—who was using a decade-old company laptop—the character didn't just move slowly; it practically teleported across the screen. The problem wasn't the physics; it was that his loop was running "as fast as possible." On the gaming rig, it was hitting 500 FPS, but on the laptop, it was struggling at 30. Because his movement was tied to the frame count rather than actual time, the game speed was essentially tied to the CPU clock. It was a classic mistake, but a painful one to debug when you're staring at a screen where your character is vibrating out of existence.

The Anatomy of the Game Loop

At its core, a rendering loop is just a while loop that keeps your application alive until a specific exit condition is met. But you can't just throw a while(true) in there and call it a day. If you do, you'll peg one of your CPU cores to 100% and likely freeze your windowing system. A proper loop needs to synchronize three distinct phases: processing input, updating the simulation state, and drawing the results to the screen.

I usually structure this as a sequence of calls within the main loop. You check if the user hit the 'X' button or pressed a key, you calculate where your objects should be based on the time that has passed, and then you tell the GPU to clear the screen and redraw everything. If you skip any of these, or do them in the wrong order, you get "input lag" or "stuttering"—those tiny glitches that make a professional app feel amateur.

while (appRunning) {
    processInput();   // Handle keyboard/mouse
    updateGameState(); // Move players, check collisions
    renderFrame();    // Push pixels to the screen
}

Taming the Frame Rate with Delta Time

To avoid the "teleporting character" problem I mentioned earlier, we use something called Delta Time (often written as dt). Delta time is simply the amount of time that elapsed since the last frame was rendered. Instead of saying "move the player 5 pixels per frame," you say "move the player 100 pixels per second."

To implement this in C++, you'll typically use std::chrono. By capturing the timestamp at the start of the loop and subtracting the timestamp from the previous frame, you get a fraction of a second. You then multiply every movement value by this dt. This ensures that whether your loop runs 30 times a second or 3,000, the object moves the same physical distance in real-world time. It's a simple multiplication that saves you from a world of synchronization headaches.

Handling the Buffer Swap

One thing that often trips people up is the "flicker." If you draw directly to the screen while the monitor is refreshing, the user will see the screen being cleared and redrawn in real-time, resulting in a jarring strobe effect. This is why we use double buffering.

In a standard rendering loop, you aren't drawing to the visible screen; you're drawing to a "back buffer"—a hidden piece of memory. Once the entire frame is finished, you "swap" the buffers. The back buffer becomes the front buffer, and the front becomes the back. This swap happens almost instantaneously, giving the user a smooth, seamless transition between frames. When you're setting up your loop, make sure your rendering call ends with a swap or a "present" command, otherwise, you're just painting on a canvas that no one can see.




📋 Practical Task

Exercise: Implementing a Framerate-Independent Position Tracker

Your task is to implement a basic C++ console-based loop that simulates a moving object. Instead of using a graphics library, you will print the object's position to the console, but the logic must be framerate-independent.

  • Create a while loop that runs until a boolean isRunning becomes false.
  • Use std::chrono::steady_clock to calculate the deltaTime (the time elapsed between the current frame and the previous frame).
  • Define a position variable (float) and a speed variable (e.g., 10.0 units per second).
  • In each iteration, update the position using the formula: position += speed * deltaTime.
  • Print the current position and the current deltaTime to the console.
  • Add a small std::this_thread::sleep_for call with a random duration (between 10ms and 50ms) inside the loop to simulate fluctuating hardware performance.

Verify that despite the random sleep intervals (which simulate FPS drops), the object's position increases linearly relative to the actual wall-clock time.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.