Bug: Times passed to AnimationTimer should not fluctuate

John Hendrikx john.hendrikx at gmail.com
Thu Aug 29 15:03:08 UTC 2024


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.




More information about the openjfx-dev mailing list