What the OpenGL Swap Interval Parameter Actually Means

Concerning the OpenGL swap interval, there is much cargo-cult programming and confident misinformation. We need to explain what it really means.


Quick Reference

Here's a quick summary if you know what you're doing. A more complete explanation will follow this section.

The 'swap interval' gives the minimum number of 'video frame periods' (the time for the monitor to display the whole frame) that must elapse with the front buffer being displayed, before a buffer swap is allowed to complete.

The swap can be delayed until the next frame boundary (i.e. vertical-synchronization, VSync), which prevents screen-tearing artifacts. If the swap interval is positive, VSync is enabled. If the swap interval is negative, it's interpreted as if it were positive, except VSync is only enabled if the GPU is ahead of schedule. If the swap interval is zero, there is no VSync. If the swap is delayed, the GPU is held back (likely not at the swap but at some future point, often the next swap).

(Pseudo-)code is worth a thousand words:

int min_video_frame_periods_to_display = std::abs(swap_interval);
bool vsync_if_gpu_ahead  = swap_interval != 0;
bool vsync_if_gpu_behind = swap_interval >  0;

Here's a summary table showing example swap interval settings and render latencies and what results for swaps:

Swap Interval
Setting
Frame Periods
for Render
Frame Periods
Between Swaps
0
0
1.6
3.1
1.6
3.1
(tearing)
(tearing)
+3
+3
1.6
3.1
3
4
(VSync)
(VSync)
-3
-3
1.6
3.1
3
3.1
(VSync)
(tearing)

For multiple windows, you probably want to render all windows' frames, and then swap all windows frames. Render all, swap all—not interleaved.


Buffer Swap ≠ Refresh Rate

The monitor scans out data, drawing pixels top-to-bottom, left-to-right. The time it takes to do this, plus some overhead before beginning again, is called the 'video frame period'. More commonly and less formally: the reciprocal of that is the 'refresh rate'. Typically the frame period is about 1/60th of a second (16⅔ ms), although this varies[1].

Windowing systems generally set up at least two buffers: a 'front' buffer and a 'back' buffer. The front buffer is what the monitor is currently pulling data from to set pixels on the screen. The back buffer is what's being drawn to[2]. From time to time, the screen will 'swap the buffers', making the back buffer the new front buffer and the front buffer the new back buffer.

The vital point is that the video frame period and the buffer swap are separate. You can swap faster than the refresh rate. You can swap slower than the refresh rate. You can swap at the same rate as the refresh rate. You can change what you do moment to moment.

If a buffer swap happens during the video frame period, part of the displayed frame will be from one frame, and part will be from the next. More buffer swaps within the frame period means more frames contribute to the image displayed on-screen. This usually causes a visual artifact called 'screen tearing', which is usually considered objectionable. However, the most up-to-date information reaches your eyeballs as fast as possible on at least part of the screen, making it a popular choice for (especially) first-person shooter games.

Alternately, we can synchronize the buffer swap to the refresh rate together, so that buffer swaps are only allowed to occur exactly between video frame periods. This is VSync, and the swap interval controls how it works.


The Swap Interval

We are given cryptic commands to control this with windowing libraries like on SDL 2[3] or on GLFW[4]:

//(Omitted: create and bind a context and window above)
int swap_interval = /*...*/;
SDL_GL_SetSwapInterval(swap_interval); //SDL 2
glfwSwapInterval(swap_interval); //GLFW

These call underlying platform API extension functions in WGL (the underlying extension library on Windows) or in GLX (ditto for X11), which you can use directly yourself if you're insane enough to roll your own windowing system[5]. I.e., in the underlying platform, one of the following gets called indirectly:

wglSwapIntervalEXT(swap_interval); //WGL
glXSwapIntervalEXT(swap_interval); //GLX

The aptly-named variable swap_interval is the swap interval, and this is what we need to understand. It roughly represents the minimum number of 'video frame periods' (again, the time to scan out a single frame by the monitor) that must elapse with the front buffer being displayed, before a buffer swap is allowed to happen. However, there is some nuance.


If the swap interval is 0, then a minimum of zero video frame periods must elapse before the swap is allowed to happen—i.e., there is no restriction and the swap can happen at any time. In particular, the swap can happen while the monitor is scanning out the frame, leading to data from two different images being displayed for that split second. This is "disabling VSync".

If the swap interval is positive, then VSync is enabled. The given number of video frame periods must elapse before a swap is allowed to happen. Each frame is displayed for exactly that number of periods. (Note that it is perfectly fine for this count to be greater than 1!)

If the swap is delayed, the GPU's rendering is held back—likely not at this swap but at some future point, often after it has rendered the next frame, which will wait in the back buffer. It makes sense when you think about it: the GPU has finished rendering into the back buffer. It can't render into the front buffer because the monitor is still reading from it, but it might be able to do other rendering operations (in particular, render the next frame).

(If there is more than one back buffer, the GPU might be able to render an additional frame or more before stalling, even if the front buffer is still being used. There are also schemes (e.g. triple buffering / mailbox) where the GPU constantly renders new frames among several back buffers, and when the swap finally comes, the most recently finished back buffer can be swapped in. Thus, frames in the back buffer can be more recent than if the GPU had just waited. These are more advanced schemes, though, and OpenGL can't do them.)

The swap interval, being a signed integer, can also be negative. This is actually allowed, and is a common extension of VSync capability in the following way: a negative interval is interpreted as if it were positive and VSync is enabled—but only if the GPU is ahead of schedule.


All this might seem complicated, but (pseudo-)code is worth a thousand words:

int min_video_frame_periods_to_display = std::abs(swap_interval);
bool vsync_if_gpu_ahead  = swap_interval != 0;
bool vsync_if_gpu_behind = swap_interval >  0;

Here's a summary table showing example swap interval settings and render latencies and what results for swaps:

Swap Interval
Setting
Frame Periods
for Render
Frame Periods
Between Swaps
0
0
1.6
3.1
1.6
3.1
(tearing)
(tearing)
+3
+3
1.6
3.1
3
4
(VSync)
(VSync)
-3
-3
1.6
3.1
3
3.1
(VSync)
(tearing)

When the swap interval is zero (as in the first two lines), we never have VSync. The buffer swap simply happens when the GPU finishes rendering, probably resulting in screen-tearing artifacts.

When the swap interval is positive (as in the third and fourth lines), we always have VSync. In those lines, the swap interval is +3, meaning each frame is shown at minimum for three video frame periods (e.g. for a 60 Hz monitor, for 1/20th of a second). If the GPU is rendering faster than this (for example, in 1.6 video frame periods), the GPU is forced to wait for the remaining 1.4 periods before continuing. If the GPU is rendering slower than this (for example, in 3.1 video frame periods), then the swap is not allowed to happen in the middle of the period, even though the frame becomes ready; it must wait until the next period, the 4th—so the GPU is slowed even further.

When the swap interval is negative (as in the last two lines), we have a combination of the behaviors: we do have VSync, but only when the GPU is faster. In the example, the swap interval is -3. If the GPU is faster than 3 periods (e.g. 1.6), then it is forced to wait for the remaining 1.4 periods. However, if the GPU is slower (e.g. 3.1 periods), then the swap happens immediately.


Which Settings to Use

In a simplistically ideal case, the GPU render rate and the refresh rate would be the same. You'd have a swap interval of +1, meaning that you'd never get visual artifacts. This is a good place to start, but there's no one globally optimal choice; it's a configurable setting because the best choice is situational.

If the GPU can't keep up with the refresh rate, you might try increasing the swap interval. However, this effectively reduces the framerate of the application. Oftentimes, the GPU struggles for only a few frames as it resolves some heavy but transient visual effect. In these cases, a negative swap interval (e.g. -1) can help: most of the time, you get the benefits of VSync, but if the frame is just a little bit 'late', you get a bit of tearing but at least the framerate doesn't halve.

And of course, as above, visual tearing is sometimes acceptable in exchange for faster information. Many twitchy first-person games choose (or at least allow as an option) the choice to disable VSync entirely for that fraction-of-a-second, sub-frame advantage. It also exploits the full power of your GPU, which makes it useful for benchmarking.


Multiple Windows

The last point of confusion is what happens in the case of multiple windows. There's considerable uncertainty and probably variation among graphics vendors, but it seems like what happens is generally what you'd hope for, if you understood the above.

First, note that each window's swap interval is separate from the others[6].

The magic procedure is to first set each window's swap interval as you desire. Then, in your main loop, (1) render all the windows that need it. Then (2) swap all the buffers for those windows. Again, that's render everything, swap everything. Do not interleave them.

If VSync is required on any window, the GPU stall will happen as late as possible, which is not on the swap (as above, probably a subsequent OpenGL call or out to the next swap). For example, suppose we complete rendering the next frame for window A. If we started to render the next frame for A, we'd stall (perhaps immediately). However, we have other windows, and we can prepare the next frame for windows B and C. Indeed, we should do that, so that all windows' frames are finished by the time the period ends.

Only after all rendering is complete do we start swapping. Thus, we incur the delay for VSync at most once for all windows, instead of once for each window. This does limit the maximum speed of all windows to the speed of the slowest, but at least it won't be slower, supposing the GPU can keep up.

To get the maximum performance from all windows with different swap intervals in each, use multiple threads with a separate OpenGL context per thread[7]. If you want to do that and share data between windows by using only a single multithreaded context, well, now you need to use Vulkan 🙂


References


Return to the list of blog posts.