Skip to content

Vsync Lag Explained

yshui edited this page Jan 29, 2019 · 5 revisions

Users of compton have noticed there is some kind of input lag when vsync is enabled, this page tries to explain why that happens

Assume you don't have vsync enabled, this is what happens (remember, OpenGL is asynchronous):

Time
 |
 |
 |
 | <--- compton receives damage event
 | <--- compton sends render commands
 | <--- compton calls SwapBuffers
 | <--- render finishes
 | <--- buffer swapped
 |
 .
 .
 .
 | <--- compton receives damage event
 .
 . <--- repeat
 .
 v

Damage events happens when your application has new content to display. As you can see, the time from damage events to buffer swap is minimal.

Now if you have vsync enabled, this is what happens

Time
 |
 |
 |
 | <--- compton receives damage event (A)
 | <--- compton sends render commands (A)
 | <--- compton calls SwapBuffers (A)
 | <--- render finishes (A)
 |
 |
 .
 .
 .
 | <--- compton receives damage event (B)
 | <--- compton sends render commands (B)
 |      (Note these commands cannot start before SwapBuffers (A) is finished)
 | <--- compton calls SwapBuffers (B)
 .
 .      (A long wait)
 .
--- v-blank start
 | <--- buffer swapped (A)
 | <--- render finishes (B)
 .
 .      (Another long wait)
 .
--- next v-blank start
 | <--- buffer swapped (B)
 .
 .
 v

As you can see, contents updated in event (B) got delayed almost 2 frames, sometimes causing noticeable lag.

If we call glFinish after sending the buffer swap command, then things improves a bit:

Time
 |
 |
 |
 | <--- compton receives damage event (A)
 | <--- compton sends render commands (A)
 | <--- compton calls SwapBuffers (A)
 | <----
 .     |   <--- render finishes (A)
 .     |
 .     |
 |   compton
 .   blocked
 .     by
 .  glFinish
---    |
 |     |   <--- buffer swapped (A)
 |     |
 | <----   <--- compton unblocked
 | <--- compton receives damage event (B)
 .
 . <--- repeat
 .
 v

This makes sure that contents updated in a damage events get to the screen in one frame, reducing the lag.

Sadly, NVIDIA's glFinish implementation uses busy waiting (meaning there is a loop in the library that repeatedly checks whether rendering has finished), which causes high CPU usage, thus can't be used.