High-Resolution Timers

Learn to measure time intervals with high accuracy in your games

Ryan McCombe
Published

Previously, we introduced the SDL_GetTicks64() function, which returns the number of milliseconds that have passed since SDL was initialized.

However, in some situations, milliseconds are not granular enough. We need to use timers that use smaller units, such as microseconds and nanoseconds.

Timers that use these smaller units are typically called high-resolution timers. How they work and how we access them is something that varies from platform to platform, but SDL provides some utilities that can help us.

Getting a High Resolution Time using SDL_GetPerformanceCounter()

SDL provides access to a high-resolution timer using SDL_GetPerformanceCounter(). The values returned from this function should be accurate enough to detect even tiny changes in time:

// main.cpp
#include <SDL.h>
#include <iostream>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);

  std::cout << "\nPerformance Counter: "
    << SDL_GetPerformanceCounter(); 
  std::cout << "\nPerformance Counter: "
    << SDL_GetPerformanceCounter(); 
  std::cout << "\nPerformance Counter: "
    << SDL_GetPerformanceCounter(); 

  SDL_Quit();
  return 0;
}
Performance Counter: 23369230619011
Performance Counter: 23369230624712
Performance Counter: 23369230627816

The values returned from this function are useful when compared to other values returned by that same function. That is, we'd call SDL_GetPerformanceCounter() twice, and compare the difference. One of the main uses for this is to compare the performance of different approaches to solving a problem.

Below, our program is creating a large array, and we use SDL_GetPerformanceCounter() to determine whether reserving the required memory ahead of time reduces the performance cost:

// main.cpp
#include <SDL.h>
#include <iostream>
#include <vector>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  
  // Option A
  std::vector<int> A;
  Uint64 StartA{SDL_GetPerformanceCounter()};
  for (int i{0}; i < 1'000'000'000; ++i) {
    A.emplace_back(i);
  }
  Uint64 EndA{SDL_GetPerformanceCounter()};
  std::cout << "\nA Cost: " << EndA - StartA;  
  
  // Option B
  std::vector<int> B;
  Uint64 StartB{SDL_GetPerformanceCounter()};
  B.reserve(1'000'000'000);
  for (int i{0}; i < 1'000'000'000; ++i) {
    B.emplace_back(i);
  }
  Uint64 EndB{SDL_GetPerformanceCounter()};
  std::cout << "\nB Cost: " << EndB - StartB; 

  SDL_Quit();
  return 0;
}

We'd likely find option B to be faster, so we'd go with that approach in our program:

A Cost: 38112382
B Cost: 13096310

Getting the Time Units Using SDL_GetPerformanceFrequency()

Let's use SDL_GetPerformanceCounter() to calculate high resolution time deltas for our Tick() functions:

// main.cpp
#include <SDL.h>
#include <iostream>
#include "World.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  World GameWorld;

  SDL_Event Event;
  bool shouldContinue{true};
  Uint64 PreviousFrame{
    SDL_GetPerformanceCounter()};

  while (shouldContinue) {
while (SDL_PollEvent(&Event)) {/*...*/} Uint64 ThisFrame{SDL_GetPerformanceCounter()}; Uint64 TimeDelta{ThisFrame - PreviousFrame}; PreviousFrame = ThisFrame; std::cout << "\nTimeDelta: " << TimeDelta; GameWorld.Tick(TimeDelta); } SDL_Quit(); return 0; }
TimeDelta: 1032
TimeDelta: 932
TimeDelta: 980

However, when we do this, we also need to consider what unit of time SDL_GetPerformanceCounter() uses.

It returns values in the smallest unit the platform supports, however, that unit varies from platform to platform. It could be milliseconds; it could be nanoseconds; it could be something else entirely.

The SDL_GetPerformanceFrequency() function can help us understand what unit is being used on the platform our program is running on. It does this by returning an integer representing how many of those units there are in a second:

// main.cpp
#include <SDL.h>
#include <iostream>

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  std::cout << SDL_GetPerformanceFrequency();
  SDL_Quit();
  return 0;
}
10000000

For example:

  • A return value of 1,000 would mean it uses milliseconds
  • A return value of 1,000,000 would mean it uses microseconds
  • A return value of 1,000,000,000 would mean it uses nanoseconds

We can use this to convert our time delta to a standard, known unit of time such that our Tick() functions can behave consistently across different platforms. Below, we deliver the time delta in seconds:

// main.cpp
#include <SDL.h>
#include <iostream>
#include "World.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  World GameWorld;

  SDL_Event Event;
  bool shouldContinue{true};
  Uint64 PreviousFrame{SDL_GetPerformanceCounter()};
  const float PerformanceFrequency
    = SDL_GetPerformanceFrequency();

  while (shouldContinue) {
while (SDL_PollEvent(&Event)) {/*...*/} Uint64 ThisFrame{SDL_GetPerformanceCounter()}; float TimeDelta{(ThisFrame - PreviousFrame) / PerformanceFrequency}; PreviousFrame = ThisFrame; std::cout << "\nTimeDelta: " << TimeDelta; GameWorld.Tick(TimeDelta); } SDL_Quit(); return 0; }
TimeDelta: 0.0001639
TimeDelta: 0.0001633
TimeDelta: 0.0001631

Summary

We've covered several key concepts related to high-resolution timing:

  • Using SDL_GetPerformanceCounter() to measure time intervals
  • Understanding timer precision with SDL_GetPerformanceFrequency()
  • Calculating time deltas for game loops
  • Exploring std::chrono as an alternative to SDL timing functions

These tools are essential for creating smooth, responsive game experiences and optimizing performance in C++ and SDL applications

Next Lesson
Lesson 42 of 129

Callbacks and Function Pointers

Learn to create flexible and modular code with function pointers

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

SDL Performance Counter vs Ticks
How does using SDL_GetPerformanceCounter() differ from using SDL_GetTicks64()?
SDL vs std::chrono for Timing
What are the advantages of using std::chrono over SDL's timing functions?
SDL Performance Frequency Explained
Why do we divide by SDL_GetPerformanceFrequency() when calculating time deltas?
Frame Rate Limiting with High-Res Timers
How can we use high-resolution timers to implement frame rate limiting?
Simulating Lower Frame Rates
Is it possible to simulate lower frame rates using high-resolution timers for testing purposes?
Using Low-Resolution Timers in Games
Are there any situations where using lower resolution timers might be preferable?
Or Ask your Own Question
Purchase the course to ask your own questions