clutter/stage-cogl: Don't skip over the next frame
last_presentation_time is usually a little in the past (although
sometimes in the future depending on the driver). When it's over 2ms
sync_delay) in the past that would trigger the while loop to count up so
that the next
update_time is in the future.
The problem with that is for common values of
which are only a few milliseconds ago, incrementing
refresh_interval also means counting past the next physical frame that
we haven't rendered yet. And so mutter would skip that frame.
last_presentation_time = now - 3ms sync_delay = 2ms refresh_interval = 16ms next_presentation_time = last_presentation_time + refresh_interval = now + 13ms -3ms now +13ms +29ms +45ms ----|--+------------|---------------|---------------|---- : : last_presentation_time next_presentation_time
update_time = last_presentation_time + sync_delay = now - 1ms while (update_time < now) (now - 1ms < now) update_time = now - 1ms + 16ms update_time = now + 15ms next_presentation_time = now + 13ms available_render_time = next_presentation_time - max(now, update_time) = (now + 13ms) - (now + 15ms) = -2ms so the next frame will be skipped. -3ms now +13ms +29ms +45ms ----|--+------------|-+-------------|---------------|---- : : : : : update_time (too late) : : last_presentation_time next_presentation_time (a missed frame)
min_render_time_allowed = refresh_interval / 2 = 8ms max_render_time_allowed = refresh_interval - sync_delay = 14ms target_presentation_time = last_presentation_time + refresh_interval = now - 3ms + 16ms = now + 13ms while (target_presentation_time - min_render_time_allowed < now) (now + 13ms - 8ms < now) (5ms < 0ms) # loop is never entered update_time = target_presentation_time - max_render_time_allowed = now + 13ms - 14ms = now - 1ms next_presentation_time = now + 13ms available_render_time = next_presentation_time - max(now, update_time) = (now + 13ms) - now = 13ms which is plenty of render time. -3ms now +13ms +29ms +45ms ----|-++------------|---------------|---------------|---- : : : : update_time : : : last_presentation_time next_presentation_time
The reason nobody noticed these missed frames very often was because mutter has some accidental workarounds built-in:
Prior to 3.32, the offending code was only reachable in Xorg sessions. It was never reached in Wayland sessions because it hadn't been implemented yet (till e9e4b2b7).
Even though Wayland support is now implemented the native backend provides a
last_presentation_timemuch faster than Xorg sessions (being in the same process) and so is less likely to spuriously enter the while loop to miss a frame.
For Xorg sessions we are accidentally triple buffering (#334 (closed)). This is a good way to avoid the missed frames, but is also an accident.
sync_delayis presently just high enough (2ms by coincidence is very close to common values of
now - last_presentation_time) to push the
update_timeinto the future in some cases, which avoids entering the while loop. This is why the same missed frames problem was also noticed when experimenting with
sync_delay = 0.