Bug 229399 - Composited & non-composited animation iterations are unsynced
Summary: Composited & non-composited animation iterations are unsynced
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Animations (show other bugs)
Version: Safari Technology Preview
Hardware: Mac (Intel) macOS 11
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-08-23 02:41 PDT by Matt Perry
Modified: 2022-02-05 10:23 PST (History)
6 users (show)

See Also:


Attachments
Screen recording of background color iterating after transform and opacity (945.54 KB, video/quicktime)
2021-08-23 02:41 PDT, Matt Perry
no flags Details
Reduced test case (426 bytes, text/html)
2022-02-03 05:34 PST, Antoine Quint
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Matt Perry 2021-08-23 02:41:22 PDT
Created attachment 436172 [details]
Screen recording of background color iterating after transform and opacity

Visit this sandbox in Safari: https://codesandbox.io/s/waapi-webkit-iteration-timing-bug-forked-8ozd3?file=/src/index.js

This animation uses `transform`, `opacity` and `background`. This is slightly difficult to replicate but I've seen `transform` and `opacity` iterate up to two frames before or two frames after `background`. 

Expected: Values iterate start at the same time so they should *always* iterate on the same frame.
Comment 1 Matt Perry 2021-08-23 03:12:23 PDT
This is also reproducible using CSS animations https://codesandbox.io/s/css-animation-webkit-iteration-timing-bug-forked-4bg8j?file=/src/styles.css:0-925
Comment 2 Radar WebKit Bug Importer 2021-08-30 02:42:17 PDT
<rdar://problem/82516348>
Comment 3 Antoine Quint 2021-09-07 07:10:55 PDT
This is indeed quite visible, you can see the blocks flash as yellow for one frame when at their starting position.
Comment 4 Antoine Quint 2022-02-03 05:32:29 PST
The issue with one of the rectangles using a different color while animating is fixed by bug 229398.
Comment 5 Antoine Quint 2022-02-03 05:34:12 PST
Created attachment 450766 [details]
Reduced test case

Attaching a simplified test case which clearly shows the synchronization issue with two properties being animated at once: "transform" to move the element on the y-axis (accelerated) and "margin-left" to move the element on the x-axis (non-accelerated). It's very clear that there is a frame where the "margin-left" animation is already at progress=0 while the "transform" animation is still at progress=1.
Comment 6 Antoine Quint 2022-02-03 05:45:03 PST
Adding a delay, even just 1ms, fixes the issue with the reduction, because I think we're switching from using a pre-computed absolute start time in the startAnimation() lambda in KeyframeEffect when committing the GraphicsLayerCA animation rather than using the current media time.
Comment 7 Antoine Quint 2022-02-03 06:26:14 PST
The delay thing is a complete red herring and indicative of another bug actually: we simply don't run an accelerated animation in this case! We do add a pending action to start the accelerated animation but when we try to start it, the renderer is not composited yet and we exit, the animation never starting. I expect the nature of this bug is different than the synchronization issue we're trying to sort out here, so I filed bug 236080.
Comment 8 Antoine Quint 2022-02-05 10:18:56 PST
I don't have good news to report about this bug.

While I have fixed the egregious issue with the animation for one of the sqaure not animating in the same way as the other squares with bug 229398, the cruz of the issue with the "background-color" and "transform" animations not iterating in sync is something that I don't believe we can fix with our current architecture.

When we run the "transform" animation in this case, we delegate the actual animation to Core Animation. This has some important benefits: animations run smoothly regardless of any potential delays on the main thread (think JS work or delayed rendering due to large CSS or DOM changes), are very power-efficient, and on Apple hardware with Pro Motion displays, those animations can run at a higher frame rate than those run in software.

Meanwhile, the "background-color" property animation is not delegated to Core Animation. Instead we animate this property much like we animate the bulk of CSS properties in WebKit: while the animation is "relevant" per Web Animations terminology, we schedule page rendering updates and invalidate styles for the target element such that we can animate the CSS value. The frame rate typically obtained for these types of animations is 60fps.

There are two potential synchronization issues with this approach when mixing software and "accelerated" (delegated to Core Animation) animations.

The first one, which is the crux of this bug, is that while we ensure that both kinds of animations use the same start time and are mapped correctly to the respective time origin of the worlds in which those animations live, the problem is that those animations are _not_ resolved in a coordinated manner. So while Core Animation and WebKit itself may sample animations at 60fps, within a given frame, the animations may resolve at a different iteration progress because the time within the 1/60 second that the frame may last that they are sampled is going to be different. The difference is likely a few milliseconds and for a lot of animations, this will not prove to be a problem.

However, in this case, you have those two animations running with the same timing, so when we reach the end of an iteration, one animation may be ever so-close to an iteration progress of 1, while the other animation maybe instead a hair above an iteration progress of 0.

The second potential synchronization issue is really just a different version of this first issue when, on a Pro Motion display for instance, the software and accelerated animations may run at a different frame rate, Core Animation being able to sample animations at 120fps while the software animations would still run at 60fps.

This means that while we may be off by a frame on more traditional displays, we now have the risk of being off by two frames by virtue of Core Animation sampling animations twice as fast.

There are things we could do to mitigate these issues. We could for instance try to "predict" the interval between the times where both types of animations are sampled and try to make up for this, but this wouldn't be fool-proof. I'd have to invest more time in trying to come up with a solution similar to this, but I'm not able to commit to this at the moment and cannot suggest a time when I could do this.
Comment 9 Antoine Quint 2022-02-05 10:23:25 PST
Another thing I'd like to mention about software vs. accelerated animations is that there's also some kind of a third in-between mode. There are cases where we don't know how to delegate a transform animation to Core Animation (for various reasons, some being feature incompatibilities between Core Animation and Web Animations, some being work being required on WebKit's part). When that happens, we run the "transform" animation by updating the page rendering and resolving styles at 60fps. However, where that animation will differ from animating a purely software-implemented property, such as "margin-left" for instance, is that we know how to map a CSS transform onto a Core Animation layer property. This means that while we do resolve styles, the CSS update itself may not trigger a possibly-expensive layout, but instead updates a property on a CALayer. So while performance is guaranteed to be better for animations delegated to Core Animation, running a "transform" animation in software is still going to be more efficient than animating other types of CSS properties.