clutter/stage-cogl: Don't skip over the next frame
The 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 last_presentation_time
which are only a few milliseconds ago, incrementing update_time
by
refresh_interval
also means counting past the next physical frame that
we haven't rendered yet. And so mutter would skip that frame.
Example
Given:
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
Old algorithm:
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)
New algorithm:
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_time
much 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_delay
is presently just high enough (2ms by coincidence is very close to common values ofnow - last_presentation_time
) to push theupdate_time
into the future in some cases, which avoids entering the while loop. This is why the same missed frames problem was also noticed when experimenting withsync_delay = 0
.
Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=789186 and most of #571