Bug: Times passed to AnimationTimer should not fluctuate
Michael Strauß
michaelstrau2 at gmail.com
Thu Aug 29 16:18:48 UTC 2024
The entire idea of using a system timer in the presence of v-sync is
wrong, it simply can't work reliably. We get accurate frame
synchronization for free from Direct3D, we should use that instead of
system timers.
There's a JBS issue with a detailed analysis:
https://bugs.openjdk.org/browse/JDK-8136536
On Thu, Aug 29, 2024 at 5:40 PM John Hendrikx <john.hendrikx at gmail.com> wrote:
>
> 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