86
\$\begingroup\$

There are dozens of articles, books and discussions out there on game loops. However, I pretty often come across something like this:

while(running)
{
    processInput();
    while(isTimeForUpdate)
    {
        update();
    }
    render();
}

What basically is bothering me about this approach is the "update-independent" rendering e.g. render a frame when there is no change at all. So my question is why this approach is often taught?

\$\endgroup\$
5
  • 8
    \$\begingroup\$ Personally, I found gameprogrammingpatterns.com/game-loop.html a helpful explanation \$\endgroup\$
    – Niels
    Commented Nov 10, 2016 at 7:14
  • 13
    \$\begingroup\$ Not all changes in rendering are reflected in the game state. And I suspect you misunderstand the point of that piece of code - it allows you to update multiple times per render, not render multiple times per update. \$\endgroup\$
    – Luaan
    Commented Nov 10, 2016 at 9:21
  • 13
    \$\begingroup\$ Do note that the code reads while (isTimeForUpdate), not if (isTimeForUpdate). The main goal isn't to render() when there hasn't been an update(), but to update() repeatedly in between render()s. Regardless, both situations have valid uses. The former would be valid if state can change outside of your update function, e.g., change what is rendered based on implicit state such as the current time. The latter is valid because it gives your physics engine the possibility to do a lot of small, precise updates, which e.g. reduces the chance of 'warping' through obstacles. \$\endgroup\$
    – Thierry
    Commented Nov 10, 2016 at 9:40
  • \$\begingroup\$ A more logical question would be "what is the point of update-dependent rendering game loop" \$\endgroup\$ Commented Nov 10, 2016 at 12:43
  • 1
    \$\begingroup\$ How often do you have an update that does nothing? Not even updating background animations or on-screen clocks? \$\endgroup\$
    – pjc50
    Commented Nov 10, 2016 at 15:04

5 Answers 5

126
\$\begingroup\$

There's a long history of how we arrived at this common convention, with lots of fascinating challenges along the way, so I'll try to motivate it in stages:

1. Problem: Devices run at different speeds

Ever try to play an old DOS game on a modern PC, and it runs unplayably fast - just a blur?

A lot of old games had a very naive update loop - they'd collect input, update game state, and render as fast as the hardware would allow, without accounting for how much time had elapsed. Which means as soon as the hardware changes, the gameplay changes.

We generally want our players to have a consistent experience and game feel on a range of devices, (as long as they meet some minimum spec) whether they're using last year's phone or the newest model, a top-end gaming desktop or a mid-tier laptop.

In particular, for games that are competitive (either multiplayer or via leaderboards) we don't want players running on a particular device to have an advantage over others because they can run faster or have more time to react.

The surefire solution here is to lock the rate at which we do gameplay state updates. That way we can guarantee the results will always be the same.

2. So, why not just lock the framerate (eg. using VSync) and still run the gameplay state updates & rendering in lockstep?

This can work, but is not always palatable to the audience. There was a long time when running at a solid 30 fps was considered the gold standard for games. Now, players routinely expect 60 fps as the minimum bar, especially in multiplayer action games, and some older titles now look noticeably choppy as our expectations have changed. There's also a vocal group of PC players in particular who object to framerate locks at all. They paid a lot for their bleeding-edge hardware, and want to be able to use that computing muscle for the smoothest, highest-fidelity rendering it's capable of.

In VR in particular, framerate is king, and the standard keeps creeping up. Early in the recent resurgence of VR, games often ran around 60 fps. Now 90 is more standard, and harware like the PSVR is beginning to support 120. This may continue to rise yet. So, if a VR game limits its framerate to what's doable & accepted today, it's liable to be left behind as hardware and expectations develop further.

(As a rule, be wary when told "players can't perceive anything faster than XXX" as it's usually based on a particular type of "perception," like recognizing a frame in sequence. Perception of continuity of motion is generally far far more sensitive. )

The last issue here is that a game using a locked framerate also needs to be conservative - if you ever hit a moment in the game where you're updating & displaying an unusually high number of objects, you don't want to miss your frame deadline and cause a noticeable stutter or hitch. So you either need to set your content budgets low enough to leave headroom, or invest in more complicated dynamic quality adjustment features to avoid pegging the whole play experience to the worst-case performance on min-spec hardware.

This can be especially problematic if the performance problems show up late in development, when all your existing systems are built & tuned assuming a lockstep rendering framerate that now you can't always hit. Decoupling update & rendering rates gives more flexibility for dealing with performance variability.

3. Doesn't updating at a fixed timestep have the same problems as (2)?

I think this is the meat of the original question: if we decouple our updates and sometimes render two frames with no game state updates in between, then isn't it the same as lockstep rendering at a lower framerate, since there's no visible change on the screen?

There's actually several different ways games use the decoupling of these updates to good effect:

a) The update rate can be faster than the rendered framerate

As tyjkenn notes in another answer, physics in particular is often stepped at a higher frequency than the rendering, which helps minimize integration errors and give more accurate collisions. So, rather than having 0 or 1 updates between rendered frames you might have 5 or 10 or 50.

Now the player rendering at 120 fps can get 2 updates per frame, while the player on lower spec hardware rendering at 30 fps gets 8 updates per frame, and both their games run at the same gameplay-ticks-per-realtime-second speed. The better hardware makes it look smoother, but doesn't radically alter how the gameplay works.

There's a risk here that, if the update rate is mismatched to the framerate, you can get a "beat frequency" between the two. Eg. most frames we have enough time for 4 game state updates and a little leftover, then every so often we have enough saved up to do 5 updates in a frame, making a little jump or stutter in the movement. This can be addressed by...

b) Interpolating (or extrapolating) the game state between updates

Here we'll often let the game state live one fixed timestep in the future, and store enough information from the 2 most recent states that we can render an arbitrary point between them. Then when we're ready to show a new frame on-screen, we blend to the appropriate moment for display purposes only (ie. we don't modify the underlying gameplay state here)

When done right this makes the movement feel buttery smooth, and even helps to mask some fluctuation in framerate, as long as we don't drop too low.

c) Adding smoothness to non-gameplay-state changes

Even without interpolating gameplay state, we can still get some smoothness wins.

Purely visual changes like character animation, particle systems or VFX, and user interface elements like HUD, often update separately from the gameplay state's fixed timestep. This means if we're ticking our gameplay state multiple times per frame, we're not paying their cost with every tick - only on the final render pass. Instead, we scale the playback speed of these effects to match the length of the frame, so they play as smoothly as the rendering framerate allows, without impacting game speed or fairness as discussed in (1).

Camera movement can do this too - especially in VR, we'll sometimes show the same frame more than once but reproject it to take into account the player's head movement in between, so we can improve the perceived latency and comfort, even if we can't natively render everything that fast. Some game streaming systems (where the game is running on a server and the player runs only a thin client) use a version of this too.

4. Why not just use that (c) style for everything? If it works for animation and UI, can't we simply scale our gameplay state updates to match the current framerate?

Yes* this is possible, but no it's not simple.

This answer is already a bit long so I won't go into all the gory details, just a quick summary:

  • Multiplying by deltaTime works to adjust to variable-length updates for linear change (eg. movement with constant velocity, countdown of a timer, or progress along an animation timeline)

  • Unfortunately, many aspects of games are non-linear. Even something as simple as gravity demands more sophisticated integration techniques or higher-resolution substeps to avoid diverging results under varying framerates. Player input and control is itself a huge source of non-linearity.

  • In particular, the results of discrete collision detection and resolution depend on update rate, leading to tunneling and jittering errors if frames get too long. So a variable framerate forces us to use more complex/expensive continuous collision detection methods on more of our content, or tolerate variability in our physics. Even continuous collision detection runs into challenges when objects move in arcs, requiring shorter timesteps...

So, in the general case for a game of medium complexity, maintaining consistent behaviour & fairness entirely through deltaTime scaling is somewhere between very difficult & maintenance intensive to outright infeasible.

Standardizing an update rate lets us guarantee more consistent behaviour across a range of conditions, often with simpler code.

Keeping that update rate decoupled from rendering gives us flexibility to control the smoothness and performance of the experience without altering the gameplay logic.

Even then we never get truly "perfect" framerate independence but like so many approaches in games it gives us a controllable method to dial in toward "good enough" for the needs of a given game. That's why it's commonly taught as a useful starting point.

\$\endgroup\$
14
  • 2
    \$\begingroup\$ In cases where everything will be using the same frame rate, synchronizing everything can minimize the delay between when the controller is sampled and when the game state reacts. For many games on some older machines, the worst-case time would be under 17ms (controls are read at the start of vertical blank, then game state changes are applied, and the next frame is rendered while the beam is moving down the screen). Decoupling things will often result in a significant increase in the worst-case time. \$\endgroup\$
    – supercat
    Commented Nov 10, 2016 at 16:41
  • 3
    \$\begingroup\$ While it's true that more complicated update pipelines make it easier to unintentionally introduce latency, it's not a necessary consequence of the decoupled approach when implemented correctly. In fact, we may even be able to reduce latency. Let's take a game that renders at 60 fps. With a lockstep read-update-render, our worst case latency is 17ms (ignoring graphics pipeline and display latency for now). With a decoupled (read-update)x(n>1)-render loop at the same framerate, our worst case latency can only be the same or better because we check & act on input as frequently or more. :) \$\endgroup\$
    – DMGregory
    Commented Nov 10, 2016 at 17:44
  • 3
    \$\begingroup\$ On an interesting side note on old games not accounting for real time elapsed, the original Space Invaders arcade had a glitch caused by rendering power, where as the player destroyed enemy ships, rendering, and thus game updates, would speed up, resulting, accidentally, in the game's iconic difficulty curve. \$\endgroup\$
    – Oskuro
    Commented Nov 10, 2016 at 17:58
  • 1
    \$\begingroup\$ @DMGregory: If things are done asynchronously, then it will be possible for a control change to occur just after the game loop polls the controls, the game event cycle after the current one to finish just after the rendering loop grabs the game state, and for the rendering loop after the current one to finish just after the video output system grabs the current frame buffer, so the worst-case time ends up being just shy of two game loop times plus two rendering times plus two frame periods. Synchronizing things properly could cut that in half. \$\endgroup\$
    – supercat
    Commented Nov 10, 2016 at 18:55
  • 1
    \$\begingroup\$ @Oskuro: That was not a glitch. The speed of the update loop remains constant regardless of how many invaders are on screen, but the game doesn't draw all the invaders on every update loop. \$\endgroup\$
    – supercat
    Commented Nov 10, 2016 at 18:59
8
\$\begingroup\$

The other answers are good and talk about why the game loop exists and should be seperate from the render loop. However, as for the specific example of "Why render a frame when there hasn't been any changes?" It really just comes down to hardware and complexity.

Video cards are state machines and they're really good at doing the same thing over and over again. If you only render things that have changed, it's actually more expensive, not less. In most scenarios, there's not much of anything that's static, if you move to the left slightly in an FPS game, you've changed the pixel data of 98% of stuff on the screen, you might as well render the whole frame.

But mainly, complexity. Keeping track of everything that's changed while doing an update is a lot more expensive because you have to either rework everything or keep track of the old result of some algorithm, compare it to the new result and only render that pixel if the change is different. It depends on the system.

The design of hardware etc. is largely optimised for current conventions, and a state machine has been a good model to start from.

\$\endgroup\$
4
  • 5
    \$\begingroup\$ There's a distinction to be made here between rendering (everything) only when something has changed (what the question asks about), and rendering only the parts that have changed (what your answer describes). \$\endgroup\$
    – DMGregory
    Commented Nov 10, 2016 at 14:23
  • \$\begingroup\$ That's true, and I tried to make that distinction between the first and second paragraphs. It's rare for a frame not to change at all, so I thought it was important to take this view alongside your comprehensive answer. \$\endgroup\$
    – Waddles
    Commented Nov 10, 2016 at 22:31
  • \$\begingroup\$ In addition to this I'd note that there's no reason not to render. You know that you always have time for your render calls every frame (you'd better know that you always have time for your render calls every frame!) so there's very little harm in doing an 'unnecessary' render - especially since this case will essentially never come up in practice. \$\endgroup\$ Commented Nov 11, 2016 at 1:37
  • \$\begingroup\$ @StevenStadnicki It's true that there's not a big time cost to rendering everything every frame (since you need to have time in your budget to do that anyway whenever lots of state changes at once). But for portable devices like laptops, tablets, phones, or portable game systems, it could be considerate to avoid redundant rendering to make efficient use of the player's battery. This mainly applies to UI-heavy games where large parts of the screen might remain unchanging for frames between player actions, and won't always be practical to implement depending on the game's architecture. \$\endgroup\$
    – DMGregory
    Commented Jan 27, 2017 at 23:54
6
\$\begingroup\$

Rendering is usually the slowest process in the game loop. Humans don't easily notice a difference in a frame rate faster than 60, so it often less important to waste time on rendering faster than that. However, there are other processes that would benefit more from a faster rate. Physics is one. Too big of a change in one loop can cause objects to glitch right past walls. There may be ways to get around simple collision errors on larger increments, but for lots of complex physics interactions, you just aren't going to get the same accuracy. If the physics loop is run more frequently though, there is less of a chance of glitches, since objects can be moved in smaller increments without being rendered every time. More resources go toward the sensitive physics engine and less are wasted on drawing more frames the user can't see.

This is especially important in more graphics-intensive games. If there was one render for every game loop, and a player did not have the most powerful machine, there may be points in the game where the fps drops to 30 or 40. While this would still be a not entirely horrible frame rate, the game would start to get fairly slow if we tried keeping each physics change reasonably small to avoid glitching. The player would be annoyed that his character walks only half the normal speed. If the rendering rate was independent from the rest of the loop, however, the player would be able to stay at a fixed walk speed despite the drop in frame rate.

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Or, for instance, measure time between ticks and calculate how far the character should go in that time? The days of fixed sprite animations are long past! \$\endgroup\$
    – Graham
    Commented Nov 10, 2016 at 16:32
  • 2
    \$\begingroup\$ Humans can't perceive things faster than 60fps without temporal aliasing, but the frame rate necessary to achieve smooth motion in the absence of motion blur may be much higher than that. Beyond a certain speed, a spinning wheel should appear as a blur, but if software doesn't apply motion blur and the wheel turns by more than half a spoke per frame, the wheel may look bad even if the frame rate were 1000fps. \$\endgroup\$
    – supercat
    Commented Nov 10, 2016 at 16:45
  • 1
    \$\begingroup\$ @Graham, then you run into the problem from my first paragraph, where things such as physics misbehave with the bigger change per tick. If the framerate drop low enough, compensating with bigger changes might cause a player to run right through a wall, completely missing its hit box. \$\endgroup\$
    – tyjkenn
    Commented Nov 10, 2016 at 17:16
  • 1
    \$\begingroup\$ Human can't usually perceive faster than 60 fps, so it is pointless to waste resources on rendering faster than that. I take issue with that statement. VR HMDs render at 90Hz, and it's going up. Believe me when I tell you that you can CERTAINLY perceive the difference between 90Hz and 60Hz on a headset. Also, I've seen as many games lately that are CPU-bound as GPU-bound. Saying "rendering is usually the slowest process" is not necessarily true. \$\endgroup\$
    – 3Dave
    Commented Nov 10, 2016 at 21:18
  • \$\begingroup\$ @DavidLively, so "pointless" may have been too much of an exaggeration. What I meant was that rendering tends to be a bottleneck, and most games look fine at 60 fps. Certainly there are effects that are important in some kinds of games like with VR that you can only get with faster frame rates, but those seem like the exception rather than the norm. If I was running a game on a slower machine, I would much rather have working physics than a barely noticeable fast frame rate. \$\endgroup\$
    – tyjkenn
    Commented Nov 10, 2016 at 21:52
4
\$\begingroup\$

A construction like the one in your question can make sense if the rendering subsystem has some notion of "elapsed time since last render".

Consider, for instance, an approach in which the position of an object in the game world is represented through fixed (x,y,z) coordinates with an approach that additionally stores the current movement vector (dx,dy,dz). Now, you could write your game loop so that the change of the position has to occur in the update method, but you could also design it so that the change of the movement has to occur during update. With the latter approach, even though your game state actually won't change until the next update, a render-function that is called at a higher frequency could already draw the object at a slightly updated position. While this technically leads to a discrepancy between what you see and what is represented internally, the difference is small enough to not matter for most practical aspects, yet allows animations to look much smoother.

Predicting "the future" of your game state (despite the risk of being wrong) can be a good idea when you take for instance the latencies of network input into account.

\$\endgroup\$
4
\$\begingroup\$

In addition to other answers...

Checking for change of state requires significant processing. If it takes similar (or more!) processing time to check for changes, compared to actually doing the processing, you really haven't made the situation better. In the case of rendering an image, as @Waddles says, a video card is really good at doing the same dumb thing over and over again, and it's more costly to check each chunk of data for changes than it is to simply transfer it across to the video card for processing. Also if the rendering is gameplay then it's really unlikely for the screen not to have changed in the last tick.

You're also assuming that rendering takes significant processor time. This very much depends on your processor and graphics card. For many years now, the focus has been on offloading progressively more sophisticated rendering work to the graphics card and reducing the rendering input needed from the processor. Ideally the processor's render() call should simply set up a DMA transfer and that's it. Getting data to the graphics card is then delegated to the memory controller, and producing the image is delegated to the graphics card. They can be doing that in their own time, while the processor in parallel carries on with the physics, gameplay engine and all the other stuff which a processor does better. Obviously the reality is a lot more complicated than that, but being able to offload work to other parts of the system is also a significant factor.

\$\endgroup\$

You must log in to answer this question.

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