Skip to content
Lesson 1 of 14

Step 1 of 5 · Reading · ~4 min

Read

Game Loop

What Game Engines Solve

A game engine is the program that turns player input into pictures and sound sixty or more times a second, on a deadline. After this lesson your program will do the first job every engine does: read the timestamps of presented frames, turn them into frame times, and report the average frame rate, the worst frame and every frame that blew its budget.

One frame, one pass through every system

Everything an engine does is organised around the frame: one trip through a fixed sequence of poll input, advance the simulation, draw, present. The subsystems hang off that sequence:

  • Input turns device state into events and queries.
  • Simulation is game logic, scripts, AI, physics and animation.
  • Rendering and audio consume the finished state.
  • Assets (textures, meshes, sounds) are loaded before a frame needs them.
  • Networking feeds remote state in.

Unity, Unreal, Godot, Bevy and in-house engines organise these differently, but all of them share the loop. This course builds the pieces in order and ends with a game that uses all of them at once.

Measure in milliseconds, not frames per second

Frame time and frame rate are reciprocals: fps = 1000 / ms. They are not linearly related.

frame time    frame rate
  8.0 ms       125 fps
 16.667 ms      60 fps
 33.333 ms      30 fps
 50.0 ms        20 fps

Going from 16.7 ms to 33.3 ms loses 30 fps; going from 33.3 ms to 50 ms loses only 10. Profilers show milliseconds because milliseconds add up: a frame is the sum of its parts.

The same fact decides how to average. Take ten frames: nine of 10 ms and one hitch of 100 ms. That is 190 ms for 10 frames, so 19 ms per frame, or 52.6 fps. Convert each frame to fps first (nine frames at 100 fps, one at 10 fps) and average those, and you get 91 fps, a number that hides the hitch the player felt. Add up time, divide by the number of frames, convert once.

Frame times come from timestamps

The platform does not tell you how long the last frame took. It gives you a monotonic clock: a counter that only moves forward and ignores the user changing the system time. The engine reads it once per presented frame, so what you really have is a list of timestamps, and a frame time is the difference between two neighbours.

Two consequences follow:

  1. The first timestamp is only a baseline. No frame precedes it, so it produces no duration and does not count as a frame.
  2. Clock values are large. Microseconds since the Unix epoch are about 1.7 x 10^15, far beyond a 32-bit integer.

Budget and hitch

A 60 Hz display refreshes every 16.667 ms, and that is the frame budget. A frame that takes longer than its budget is a hitch. A frame that takes exactly the budget made its deadline.

With vsync on, a frame that finishes just after a refresh waits for the next one, so a single 17 ms frame stays on screen for 33 ms. The player sees 30 fps motion although the average is almost 60. That is why engines report the worst frame next to the average.

Worked trace

A 50 Hz game with a budget of 20000 microseconds presents frames at the timestamps 1000, 21000, 36000, 41000 and 101000:

stamp    duration    frames   total     worst    avg fps   hitch
 1000    (baseline)
21000    20.000 ms      1     20000    20.000      50.0     no  (equals the budget)
36000    15.000 ms      2     35000    20.000      57.1     no
41000     5.000 ms      3     40000    20.000      75.0     no
101000   60.000 ms      4    100000    60.000      40.0     yes

Average fps is 1,000,000 times the frame count, divided by the total microseconds. One 60 ms hitch drags the average down to 40.

Common mistakes

  • Counting the first timestamp as a frame. The output must have one line fewer than there are timestamps.
  • Averaging the per-frame fps values instead of the frame times. Nine fast frames and one slow one report about 91 instead of 52.6.
  • Storing timestamps in 32-bit integers. A clock reading of 1731000000123456 wraps and produces negative durations.
  • Testing duration >= budget. A frame that exactly meets its budget is not a hitch.
  • Printing milliseconds from a rounded float. Work from the integer microseconds: 1005 us is 1.005 ms, 999 us is 0.999 ms.

Your exercise

Build the profiler described in the exercise. The starter parses the stream, takes the differences and prints each line; you write the per-frame statistics update. The most likely mistake is treating the first timestamp as a frame.

Up nextThe Game LoopGame Loop

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…