TLDR; AnimationTimer is being fed a time not based on the time the next frame is expected to be rendered but the time of when the FX thread is finally scheduled to do the callbacks.  This time fluctuates wildly, and is a time that's useless for doing frame accurate calculations of animations (such a time shouldn't fluctuate at all, unless a frame gets skipped).

Longer version:

I've been investigating an "issue" with AnimationTimers.

Timers have a `void handle(long)` method that is called with a time in nano seconds that is supposed to be the reference value to be used for animations. The reference value is used to calculate in the implementation how the animation should look at a specific point in time.

However, I've found that the values being received by the AnimationTimer fluctuate wildly, and the differences between the current and previous calls of the time passed to the handle method can be anywhere from 1ms to 30ms.

The frequency of the calls is however still close to 60 fps -- now, correct me if I'm wrong, but when drawing animations that are supposed to be displayed on a 60 fps screen, shouldn't the AnimationTimer be called (which is supposed to "prepare" things for the **next** frame) with a relatively consistently increasing value?  (ie. 0, 16, 32, 48, 64 ms) instead of what I'm seeing now (0, 30, 33, 60, 66 ms).  Assuming that the animation will simply be visible on the next frame, then my calculations using this fluctuating timer are going to be quite jittery when put on a screen that refreshes at exactly 60 fps.  My animation calculations would look like:

    |------|-|------|-|--------|-|------|-|--

While being displayed on a screen that does this:

    |---|---|---|---|---|---|---|---|---|---

I compared the values I receive from `handle(long)` with `System.nanoTime()` and found no significant differences between them; in other words, the actual calling code is already quite a jittery process.

So I dug a bit deeper -- internally, a subclass of Timer is used (in my case WinTimer, the Windows one).  Assuming that the problem stemmed from there, I replaced this with a **highly** accurate timer that simply busy waits until the appropriate frame time is reached.  The accuracy of this is almost nanosecond perfect (ie, within 0.000001 of a millisecond, at the cost of a CPU core of course, but that's not relevant for the problem).  This however had 0 impact on the accuracy of the AnimationTimer -- it was still fluctuating wildly, on the order of 20-30 milliseconds (several million times less accurate than my Timer implementation).

Digging further, the AnimationTimer is not actually called directly from the timing thread (which is a different thread than the FX thread, so that makes sense).  Instead, the timer is called from the FX thread by having my highly accurate timer call Application::invokeLater (similar to Platform::runLater).  Once the FX thread picks up this task, it calls `System.nanoTime()` as a "base" for all AnimationTimer callbacks... however, this value is now distorted by how long it takes the scheduler to wake up the FX thread and pick up the invokeLater task.

I find this highly surprising.  To make jitter free animations, you want a base time that increments consistently, and maybe sometimes skips a frame.  So you'd expect the base time value to increment with 1 / frameRate, not some arbitrary time for when the work was started (fluctuating with thread scheduling delays).  I mean, I'm preparing things for the **next** frame, so the time I receive should reflect when that frame is likely to be drawn, not roughly be the same time my method is actually being called (if I wanted that, I can call `System.nanoTime()` myself...).

I feel this is actually a bug.  It is probably a bit worse on the Windows platform where the scheduler generally has a 15 ms period (which explains the fluctuations I'm seeing between 0 and 30 ms). However, this really shouldn't matter.  When doing animations, you want to call not with the current system time, but with the expected render time of the next frame.  Calling `System.nanoTime` and using that as base time is just simply incorrect, although can be "close" to correct if your platform schedules say with a 1 ms period (it will only fluctuate between 0 and 2 ms then).

There should however be 0 fluctuation, unless a frame was skipped.  With a 60 fps frame rate, all the nano values passed to AnimationTimer should be multiples of 16,666,666 ns.

Let me know what you think, perhaps I'm missing something here.

--John

PS. I think it is possible to do the pulse waiting on the FX thread itself; the FX thread can be interrupted when a Platform::runLater call comes in, but at all other times it can just wait for the next pulse directly on that thread.  With my highly accurate timer implementation (I made another variant that is accurate to within a few tenths of a milliseconds without busy waiting) Animation code would be called much faster, so it would have more time run before the next frame is displayed... at it is now, at least on Windows, the AnimationTimer can get called just a few milliseconds before the next frame is displayed, leaving precious little time to do any kind of calculations.


Reply via email to