javascriptanimationsettimeoutrequestanimationframeweb-animations

rAF loop for consistent requestAnimationFrame precision and timing


I have found contradicting information regarding timing with requestAnimationFrame in JS and would appreciate some help. In this post (1 Exact time of display: requestAnimationFrame usage and timeline) the timing of the rAF timestamp was discussed as varying between browsers. But according to documentation it should "indicating the end time of the previous frame's rendering".

The thread "Exact time of display..." discusses an independent rAF loop that fills the list of rAF-requests so that the document is handled as "animated" if i understood that correctly. This is why the rAF timestamp should not vary but always depict the time directly before the next pain (in the same event loop as suggested by user Kaiido). Be sure to correct me, if i got that wrong.

What i would like to get is a consistent timestamps that don't vary between browsers and best-case also the time in which a frame is painted. I don't really mind some kind of latency between call and display of the frame/object on screen but i would need really high precision (the variability of error based on the JS measurement compared to external measurements). An analysis of some JS libraries suggested that a precision of under 5ms is generally possible. Similar to this study, I would also use low-latency input device.

The original author (of article 1) also did a study on that topic but i would like to further understand the difference that the rAF loop causes. Why would an empty rAF queue lead to inconsistent timestamp behavior? Is the independent rAF loop a "hacky" solution that might stop working after a few months?


Solution

  • according to documentation it should "indicating the end time of the previous frame's rendering".

    Actually, according to the specs1, it should be the time when the browser deemed it had a render opportunity:

    1. Wait until at least one navigable whose active document's relevant agent's event loop is eventLoop might have a rendering opportunity.

    2. Set eventLoop's last render opportunity time to the unsafe shared current time.

    And then that last render opportunity time is being used as frameTimestamp, itself used by the run the animation frame callbacks algo.

    So, when does the browser deem it has a render opportunity? For the specs, it happens in the magical world of in parallel, i.e. they don't really say.

    In real world... it depends. For systems that do use a VSync signal, Chrome (and probably Firefox) will try to guess when the next signal will be emitted based on the previous one it received. They'll thus schedule a task to fire at this time. However, keeping track of this VSync and queuing new tasks is quite consumptive, so after some time without a need to render anything new, the rendering pipeline will be put on halt, so to speak. When a new rendering needs to be performed, the browser has lost track of the last VSync signal and thus can't estimate correctly when the next one will happen. So it will simply run it as soon as possible.
    That's why keeping the document "animated" forces the browser to keep the rendering pipeline active and to keep track of the previous VSync and thus enables it to correctly guess the next one. So, yes, in Chrome with its current behavior, running an empty rAF loop will allow correct timing between each frame, with a lot more precision than 5ms.

    Note that Firefox behavior did change since I wrote the linked answer, they now behave a more like Chrome in that they'll also run the rAF as soon as possible from an non-animated document. So there too, this solution would work.

    In Safari it's a bit less clear to me what they're doing. Desktop Safari is still clamped to 60Hz whatever the actual monitor's refresh rate. I suppose they just don't look at the VSync signal at all there, and instead use a common timer. As for the timing precision of this timer, I can't say much about it unfortunately. IIRC, some mobile versions do allow higher frame rate, but I never experimented with these myself.

    Now, some systems may have variable refresh rates (e.g. NVIDIA®'s G-SYNC® or AMD's FreeSync™), some systems may not have a monitor at all, or one with no VSync etc. in which case it's hard to tell how the different browsers will all react, and nobody can say how browsers's architecture will evolve in the future. So indeed this "workaround" may not handle all the edge cases, and certainly not forever. Still it should handle the most common ones, and this for quite some time I believe.

    1. Note that we did edit the specs heavily since I wrote my previous linked answer, and since the papers you linked to have been written.