Re: Doubts about GPeriodic



hi Havoc,

First I want to note that GPeriodic was only an attempt to get the
timing right.  It's just a clock.  It is not in any way meant to provide
policy about how a paint cycle is actually run.

That said, I did make a Gtk branch with some crackful experimentation
(currently shoving GPeriodic into gdkthreads in a global way).  This is
not meant to be "the way" -- it was just a convenient place to stick it
for now so that we could experiment with getting some widgets animating
using it.  Of course, we're discovering that resize handling and stuff
is quite difficult (another mail for all that stuff).

It's worth noting, though, that Emmanuele was able to get Clutter's
paint cycle working on top of it without modification, so there is
something here...

Anyway.  GPeriodic is just a clock, so let's talk timing.

On Thu, 2010-10-21 at 03:09 -0400, Havoc Pennington wrote:
> Another thought, in the patch
>    periodic->last_run = now;
> I think this will look a little rocky - the frames are going to
> display at actual-screen-hz intervals, no matter what time it is when
> you record last_run and no matter what time it is when you call your
> drawing APIs. So things look better if you keep the "tween timestamp"
> on hz intervals. The last_run time probably has very little to do with
> when frames hit the display. Animations should go ahead and paint
> assuming they are hitting the display at a fixed fps.

This is something that gave me a great deal of pause, and is a rather
interesting question.  I'll attach the full code fragment in its current
form, for context:

  /* Update the last_run.
   *
   * In the normal case we want to add exactly 1 tick to it.  This
   * ensures that the clock runs at the proper rate in the normal case
   * (ie: the dispatch overhead time is not lost).
   *
   * If more than one tick has elapsed, we set it equal to the current
   * time.  This has two purposes:
   *
   *   - sets last_run to something reasonable if the clock is running
   *     for the first time or after a long period of inactivity
   *
   *   - resets our stride if we missed a frame
   */
  now = g_periodic_get_microticks (periodic);
  elapsed_ticks = (now - periodic->last_run) / 1000000;
  g_assert (elapsed_ticks > 0);

  if G_LIKELY (elapsed_ticks == 1)
    periodic->last_run += 1000000;
  else
    periodic->last_run = now;

[[and yes, I'm using G_LIKELY strictly for documentation purposes]]

In the usual case, it's true that the ticker (which is expressed in
microframes, by the way) is advanced exactly one frame when dispatching
from the main context (ie: free-running clock with no external synch
information from vblank).

The only place I disagree with you is on what to do when we want to skip
a frame.

> In the litl shell fwiw the pseudocode for the tween time on each frame is:
> 
> int frame_time = 1000 / fps;
> int actual_time = <time since start of animation> - current_ticker_time;
> int frames_late = (actual_time / frame_time) - 1;
> current_ticker_time += frame_time;
>  if (frames_late > 0) {
>     current_ticker_time += (frame_time * (frames_late + 1));
>  }
>
> The idea of this is: decide to drop frames based on floor(frames_late)
> and then skip ahead by ceil(frames_late). The point of that is to bias
> against dropping a frame until we're a full frame behind, but then be
> sure we drop enough frames to get ahead a bit when we do drop them,
> and always stay on a multiple of the refresh rate.

This is an interesting proposal.

The problem when the clock is free-running is that we don't know exactly
what side of the vblank we're on.  That's the point of resetting the
stride (ie: assuming that the new top-of-the-frame time is now).  That
might end up being right, or it might be wrong.  In the event that we
are late for only one frame, it's a cointoss.

On the other hand, if we are consistently dropping frames -and- are
unaware of the vblank, I think I could mount a statistical argument that
my approach is more likely to result in a more smooth/accurate animation
(say, RMS of correct position vs. actual position for each frame that
actually hits the monitor).

Far more interesting, I think is in unblock():

      periodic->last_run = g_periodic_get_microticks (periodic);

In the event that we *do* have vblank information, we set the counter to
exactly the wallclock time at some semi-random interval (namely:
whenever our process bothered to notice the notification from the X
server).

I'm actively unhappy with that.  I was talking with Emmanuele about the
information that's in the vblank notification we get from the server.
There is timestamp information there.  I'd be quite a lot happier if we
had a method to inject that information into GPeriodic (ie: a timestamp
parameter on the unblock API).

> Due to this and also the desire to not explode when the computer's
> clock is set, I would define the ticker to be a monotonic value that
> is in time units but is not a wall clock time. i.e. if I change my
> computer's clock back an hour, the ticker should keep marching
> forward, and the ticker is allowed to be fudged to make animations
> pretty.

I think we have some agreement that we should move to using the kernel
monotonic time instead of gettimeofday().  The fact that GMainContext
already mixes these two quite a lot (ie: using gettimeofday() to make
calculations of how many milliseconds to give to poll()) has sort of
pre-muddied the water there, so I'm currently just wading into that
existing mess.

No point in making it worse, however.


Cheers



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]