Frame Rate Limiting with High-Res Timers
How can we use high-resolution timers to implement frame rate limiting?
Frame rate limiting is an important technique in game development to ensure consistent performance and reduce unnecessary resource consumption. High-resolution timers, like SDL_GetPerformanceCounter()
, can be used to implement precise frame rate limiting. Here's how you can do it:
Basic Concept
The idea is to calculate how long each frame should take (target frame time) and then wait if the frame finishes early. For example, if we want 60 FPS, each frame should take approximately 1/60 seconds or about 16.67 milliseconds.
Implementation
Here's a basic implementation using SDL's high-resolution timer:
#include <SDL.h>
#include <iostream>
const int TARGET_FPS{60};
const double TARGET_FRAME_TIME
{1.0 / TARGET_FPS};
void simulateGameLogic() {
// Simulate some game logic
SDL_Delay(5); // Simulate 5ms of work
}
void render() {
// Simulate rendering
SDL_Delay(3); // Simulate 3ms of rendering
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_TIMER);
Uint64 performanceFrequency{
SDL_GetPerformanceFrequency()};
Uint64 frameStart;
double elapsedSeconds;
for (int frame{0}; frame < 600; ++frame) {
frameStart = SDL_GetPerformanceCounter();
simulateGameLogic();
render();
elapsedSeconds = (
SDL_GetPerformanceCounter() -
frameStart) /
static_cast<double>(performanceFrequency);
if (elapsedSeconds < TARGET_FRAME_TIME) {
SDL_Delay(
static_cast<Uint32>((TARGET_FRAME_TIME -
elapsedSeconds) * 1000));
}
if (frame % 60 == 0) {
std::cout << "FPS: "
<< 1.0 / ((SDL_GetPerformanceCounter() -
frameStart) /
static_cast<double>(
performanceFrequency))
<< '\n';
}
}
SDL_Quit();
return 0;
}
FPS: 60.0213
FPS: 59.9881
FPS: 59.9902
FPS: 60.0156
FPS: 59.9923
...
Explanation
- We define our target FPS and calculate the target frame time.
- At the start of each frame, we record the current time using
SDL_GetPerformanceCounter()
. - We run our game logic and rendering.
- We calculate how much time has elapsed during this frame.
- If we've used less time than our target frame time, we delay the remainder.
- We print the actual FPS every 60 frames to check our results.
Considerations
- This method uses
SDL_Delay()
for simplicity, but it's not always precise. For more accuracy, you could use a busy-wait loop, though this consumes more CPU:
while (
(SDL_GetPerformanceCounter() - frameStart) /
static_cast<double>(performanceFrequency)
< TARGET_FRAME_TIME) {
// Busy wait
}
- Frame rate limiting can lead to slightly uneven frame pacing. For smoother animation, you might want to use a fixed time step with interpolation.
- Some displays have variable refresh rates (e.g., 144Hz). You might want to sync with the display's actual refresh rate for optimal smoothness.
- For very high frame rates, the overhead of the timing code itself can become significant. In such cases, you might need more sophisticated approaches.
Remember, while frame rate limiting can save power and provide consistent performance, it's not always necessary. Many games run as fast as they can and use delta time for smooth animation. The best approach depends on your specific requirements.
High-Resolution Timers
Learn to measure time intervals with high accuracy in your games