2025-183

10:17:27, atom feed.

Today a simple feature I added to the game was a target frame rate. This is typically done in one of three ways.

The first is by using timers in your game code. You capture the start time of when processing begins for a single frame, then you capture the time processing ends for that frame. Subtract the times, and if the time required to prepare the frame is less than your target frame time, sleep the thread for the difference in times.

The major issue with this approach is that the thread you're collecting timing information on can be preempted at any time by the operating system. Depending on which operating system (or language library) features you use to measure time, you may accidentally be ignoring the time your thread spent off the CPU. That time passes in the world outside the computer and so can cause inconsistencies between the time your game thinks elapsed and the wall-clock time that actually elapsed.

The second way to achieve a target frame time is by using performance counters. Performance counters are typically built into modern processors and track some kind of low-level hardware event. In this way, the counter itself is independent of operating system nuances such as scheduling. The operating system provides an API to access the performance counter value. As we don't necessarily know what is being measured by the performance counter, we don't know what the count is relative to. Thus, the counter value itself is not a measure of the passage of time. The operating system also provides an API to query the frequency of increments to the performance counter, and with this information, we can compute the passage of time.

dt_s = (perf_counter_now - perf_counter_then) / perf_counter_frequency

SDL3 provides SDL_GetPerformanceCounter and SDL_GetPerformanceFrequency for this purpose.

The third way is to externalize synchronization, to rely on display hardware to indicate when a new frame can be drawn. This is done via "vertical synchronization," also called VSync. If your GPU supports VSync and you enable VSync in your renderer, your GPU synchronizes drawing the frame buffer with the refresh rate of your display hardware (ex: your monitor). If your mornitor supports running at 60 Hz and 144 Hz, and if you configure your monitor to run at 144 Hz, then your GPU will send whatever is in your graphics buffer to the display 144 times per second, making your display frame rate 144 Hz.

To use VSync effectively in your game, you likely need to make your game loop frame rate-independent. If your player has a very fast refresh rate on their monitor but your game sometimes takes a while to draw its next frame, you might not hit your frame timing. It's no problem to draw the same thing twice while you're preparing your next frame, but you need to make sure you account for the passage of time independent of refresh rate (performance counters help with this). It helps to do general math in your game in SI units--meters, seconds, etc. This way regardless of whether you hit your frame time or not, the movement of things in your game (for example) is natural. Things don't move faster than expected on fast refresh rates and slower than expected on slower refresh rates.

SDL3 provides SDL_SetRenderVsync for this purpose.

Regarding the first and second approaches to hitting a target frame rate, they are complicated by two assumptions.

The first assumption is that your player's display hardware is some harmonic of your desired target frame time. In other words, if your target frame rate is 60 FPS, then if your player's monitor is rendering at 60 FPS or perhaps 120 FPS, you most likely will not encounter screen tearing. Your 60 Hz game loop might be out of phase of the display hardware's refresh cycle, but this means you'll always have your next frame prepared by the time the display hardware draws its next image. Also, your game's cycle time is not outpacing or falling behind the cycle time of the display hardware, so you will likely never be partway through preparing a new frame when the display hardware starts reading the frame data to draw the next image. If your cycle timing is not a harmonic of the display hardware's, your game loop will, theoretically, eventually be updating a frame while the display hardware is reading the frame buffer to draw its next image.

The second assumption is that you always hit your frame time. Even if your cycle time is a harmonic of that of your display hardware, if your game loop runs longer than your target frame time, you are at risk of screen tearing.

With all this said, unfortunately none of these three techniques guarantees smooth rendering. See this great article for why.

As for me, for now, I am using the performance counter technique for simplicity.