5
$\begingroup$

I know that if you turn of vsync, it synchronizes rendering with the vertical redraw cycle to prevent tearing, and that doing so caps your rendering rate (FPS) at the monitor refresh rate, which is commonly 60hz / 60 fps, although other rates exist as well.

However, when you are not running at a full 60fps how does vsync affect your frame rate? I've heard people say that you will be limited to a multiple of 60fps (well, ~16ms to be precise), but from observation, fps can fluctuate wildly.

$\endgroup$
5
$\begingroup$

The short answer is It's Complicated™. :)

A lot of factors can affect frame timing (and the associated problem of animation juddering, due to the game's animation delta-times not matching actual frame delivery times). Some of the factors are: how CPU-limited versus GPU-limited the game is, how the game's code measures time, how much variability there is in frame time on both the CPU and GPU, how many frames of queue-ahead the CPU is allowed to build up, double versus triple buffering, and driver behavior.

On the specific question of whether you get locked to a divisor of 60 fps (30, 20, 15, etc), this seems to depend mainly on whether the application is GPU-limited or CPU-limited. If you're GPU-limited, then assuming the application is running steadily (not producing any hitches or rapid performance shifts in itself), it does lock to the highest divisor of the vsync rate that it can sustain. This timing diagram shows how it works:

vsync in GPU limited case

Time runs from left to right and the width of each block represents the duration of one frame's work. After the first few frames, the system settles into a state where the frames are turned out at a steady rate, one every two vsync periods (i.e. if vsync was 60 Hz, the game would be running at exactly 30 fps).

Note the empty spaces (idle time) in both CPU and GPU rows. That indicates that the game could run faster if vsync were turned off. However, with vsync on, the swapchain puts back-pressure on the GPU—the GPU can't start rendering the next frame until vsync releases a backbuffer for it to render into. The GPU in turn puts back-pressure on the CPU via the command queue, as the driver won't return from Present() / SwapBuffers() until the GPU starts rendering the next frame, and opens a queue slot for the CPU to continue issuing commands. The result is that everything runs at a steady 30 fps rate, and everyone's happy (except maybe the 60fps purist gamer 😉).

Compare this to what happens when the application is CPU-limited:

vsync in CPU-limited case

This time we ended up with the game running at 36 fps, the maximum speed possible given the CPU load in this case. But this isn't a divisor of the refresh rate, so we get uneven frame delivery to the display, with some frames shown for two vsync periods and some for only one. This will cause judder.

Here the GPU goes idle because it's waiting for the CPU to give it more work—not because it's waiting for a backbuffer to be available to render into. The swapchain thus doesn't put back-pressure on the GPU (or at least doesn't do so consistently), and the GPU in turn doesn't put back-pressure on the CPU. So the CPU just runs as fast as it can, oblivious to the irregular frame times appearing on the display.

It's possible to "fix" this so that the game still discretizes to vsync-friendly rates even in the CPU-limited case. The game code could try to recognize this situation and compensate by sleeping its CPU thread to artificially lock itself to 30 fps, or by increasing its swap interval to 2. Alternatively, the driver could do the same thing by inserting extra sleeps in Present() / SwapBuffers() (the NVIDIA drivers do have this as a control panel option; I don't know about others). But in any case, someone has to detect the problem and do something about it; the game won't naturally settle on a vsync-friendly rate the way it does when it's GPU-limited.

This was all a simplified case in which both CPU and GPU workloads are perfectly steady. In real life, of course, there's variability in the workloads and frame times, which can cause even more interesting things to happen. The CPU render-ahead queue and triple buffering can come into play then.

$\endgroup$
  • 1
    $\begingroup$ Excellent answer, this deserves to be framed! $\endgroup$ – Nick Miller Apr 21 '18 at 23:13
6
$\begingroup$

It depends on how a missed frame is handled by the driver.

One option is to just wait until the next vsync, causing a hitch of 32 ms and if the application is just at the limit of 16 ms can cause fluctuations.

The next option is to queue the frame for display next frame but don't wait on it. This will still cause a visual hitch but the application can then immediately start on the next frame instead of being forced to wait 16 ms. If the next frame is done faster then the late frame may even never be shown.

The final option is to not block and push the late frame to the display immediately which can cause tearing.

$\endgroup$
  • $\begingroup$ The option to queue the frame but not wait on it, would that be considered tripple (and higher) buffering? $\endgroup$ – Alan Wolfe Mar 10 '16 at 14:19
  • $\begingroup$ @AlanWolfe possibly, you need at least 3 buffers for a non-tearing and non-blocking view, one to display, one as the next to display and one that's being rendered to. $\endgroup$ – ratchet freak Mar 10 '16 at 14:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.