The Practical Guide to Implement an FPS Counter (No Fluff)
If you’ve ever spent time debugging performance, you know that a jittery FPS counter is worse than no counter at all. Most developers fall into the trap of calculating frames per second based on the duration of the single most recent frame or a fixed number of frames. Both approaches are fundamentally flawed because they treat time as a variable rather than a constant. If you want to build a reliable performance monitor, you need to stop thinking about frames and start thinking about time-based windows.
Here is the reality: FPS is an aggregate metric. When you calculate it based on the last frame, you aren't measuring performance; you’re measuring noise. A single background process spike or a garbage collection hiccup will cause your counter to swing wildly, giving you a false sense of instability. Even worse, using a fixed number of frames—like a rolling average of the last 10 frames—creates a dependency where your measurement window shrinks when the game runs fast and expands when it runs slow. You end up with a graph that is mathematically inconsistent.
To implement an FPS counter that actually provides useful data, you need to use a rolling window of time. Think of it like monitoring HTTP requests in a web service. You don't care about the latency of the last request; you care about the throughput over a specific duration.
The logic is straightforward:
- Store the timestamp of every frame completion in a queue.
- Every frame, push the current timestamp into the queue.
- Remove any timestamps from the front of the queue that fall outside your chosen window (e.g., older than one second).
- Your FPS is simply the number of elements remaining in the queue.
This approach gives you a stable, readable number that accurately reflects performance over the last second. If you want to smooth it out further, you can increase the window size to two or three seconds, though you’ll trade off some responsiveness to rapid performance drops.
Here is how you should structure your loop:
const Duration window = Duration::seconds(1);
Queue<Time> frameTimestamps;
while (true) {
// Handle input, update, and render
Time curr = Time::current();
frameTimestamps.push(curr);
while (frameTimestamps.front() + window < curr) {
frameTimestamps.pop();
}
float fps = (float)frameTimestamps.size();
}
This method is robust because it decouples the measurement from the frame rate itself. You aren't guessing how many frames fit into a second; you are counting exactly how many occurred within a fixed, absolute time interval. If you find that the UI updates too frequently, you can easily throttle the display refresh rate without affecting the underlying calculation.
Most guides get this wrong by suggesting you average frame times, but that’s a trap that hides micro-stuttering. If you want to see what’s actually happening under the hood, read our guide on profiling game performance to learn how to correlate these FPS drops with specific engine events.
Stop relying on naive frame-based averages. Implement a time-based rolling window today and you’ll finally have a performance metric you can actually trust. If you run into issues with memory allocation for the queue, consider using a circular buffer to keep the overhead near zero. Share your results in the comments if you find this approach clears up your performance graphs.