- Revision
- 290121
- Author
- [email protected]
- Date
- 2022-02-18 02:47:13 -0800 (Fri, 18 Feb 2022)
Log Message
[frame-rate] animation with different but compatible frame rates should be aligned
https://bugs.webkit.org/show_bug.cgi?id=236778
<rdar://problem/89083932>
Reviewed by Simon Fraser.
Source/WebCore:
With bug 234202 we started accounting for the frameRate property of an animation when updating
animations and ensuring that animations with the same frameRate are sampled in the same frame.
We expand on this by aligning animations that use different frameRate but could share sample frames.
For instance, if we have animations running with frameRate set to 6, 12 and 18, and assuming no
frames are ever skipped, we should expect a sequence of frames where the following animations are
sampled:
- frame 1: 12, 18, 6
- frame 2: 18
- frame 3: 12
- frame 4: 18
- frame 5: 12, 18, 6
and for that pattern to repeat.
To achieve this, we add a new "isNew" boolean flag on the FrameRateData struct. Then, in
FrameRateAligner::finishUpdate(), which is newly called from updateAnimationsAndSendEvents(),
we identify which frame rates have this flag, and analyze the other frame rates to determine
which frame rate it is most compatible with. If we find such a compatible frame rate, the new
frame rate adopts the same firstUpdateTime which ensures that they now have the same basis to
compute ideal times for sampling.
Within updateAnimationsAndSendEvents(), we also ensure that newly-added animations, regardless
of the output of FrameRateAligner::updateFrameRate(), are sampled right away so that there's no
delay for an animation to have visual output, letting them be synchronized with other animations
of that frame rate at the next opportunity.
Tests: webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html
webanimations/frame-rate/animation-frame-rate-alignment-started-together.html
webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html
* animation/DocumentTimelinesController.cpp:
(WebCore::DocumentTimelinesController::updateAnimationsAndSendEvents):
* animation/FrameRateAligner.cpp:
(WebCore::FrameRateAligner::beginUpdate):
(WebCore::FrameRateAligner::updateFrameRate):
(WebCore::frameRatesCanBeAligned):
(WebCore::FrameRateAligner::finishUpdate):
* animation/FrameRateAligner.h:
LayoutTests:
Add new tests that check the behavior of alignment when:
- two animations are started together with the same frame rate,
- two animations are started one frame apart with the same frame rate,
- three animations are each started one frame apart and with different, but compatible frame rates.
* TestExpectations:
* platform/ios-wk2/TestExpectations:
* platform/mac-wk2/TestExpectations:
* webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates-expected.txt: Added.
* webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html: Added.
* webanimations/frame-rate/animation-frame-rate-alignment-started-together-expected.txt: Added.
* webanimations/frame-rate/animation-frame-rate-alignment-started-together.html: Added.
* webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation-expected.txt: Added.
* webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html: Added.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (290120 => 290121)
--- trunk/LayoutTests/ChangeLog 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/LayoutTests/ChangeLog 2022-02-18 10:47:13 UTC (rev 290121)
@@ -1,3 +1,27 @@
+2022-02-18 Antoine Quint <[email protected]>
+
+ [frame-rate] animation with different but compatible frame rates should be aligned
+ https://bugs.webkit.org/show_bug.cgi?id=236778
+ <rdar://problem/89083932>
+
+ Reviewed by Simon Fraser.
+
+ Add new tests that check the behavior of alignment when:
+
+ - two animations are started together with the same frame rate,
+ - two animations are started one frame apart with the same frame rate,
+ - three animations are each started one frame apart and with different, but compatible frame rates.
+
+ * TestExpectations:
+ * platform/ios-wk2/TestExpectations:
+ * platform/mac-wk2/TestExpectations:
+ * webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates-expected.txt: Added.
+ * webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html: Added.
+ * webanimations/frame-rate/animation-frame-rate-alignment-started-together-expected.txt: Added.
+ * webanimations/frame-rate/animation-frame-rate-alignment-started-together.html: Added.
+ * webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation-expected.txt: Added.
+ * webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html: Added.
+
2022-02-18 Jon Lee <[email protected]>
Unreviewed gardening for GPU Process bots. Update fuzzy data.
Modified: trunk/LayoutTests/TestExpectations (290120 => 290121)
--- trunk/LayoutTests/TestExpectations 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/LayoutTests/TestExpectations 2022-02-18 10:47:13 UTC (rev 290121)
@@ -1124,6 +1124,8 @@
fast/css/accent-color/text.html [ ImageOnlyFailure ]
imported/w3c/web-platform-tests/css/css-ui/accent-color-checkbox-checked-001.tentative.html [ ImageOnlyFailure ]
+webanimations/frame-rate [ Skip ]
+
#//////////////////////////////////////////////////////////////////////////////////////////
# End platform-specific tests.
#//////////////////////////////////////////////////////////////////////////////////////////
Modified: trunk/LayoutTests/platform/ios-wk2/TestExpectations (290120 => 290121)
--- trunk/LayoutTests/platform/ios-wk2/TestExpectations 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/LayoutTests/platform/ios-wk2/TestExpectations 2022-02-18 10:47:13 UTC (rev 290121)
@@ -148,6 +148,8 @@
imported/w3c/web-platform-tests/video-rvfc/request-video-frame-callback-during-xr-session.https.html [ Skip ]
webkit.org/b/235072 imported/w3c/web-platform-tests/video-rvfc/request-video-frame-callback-repeating.html [ Skip ]
+webanimations/frame-rate [ Pass ]
+
#//////////////////////////////////////////////////////////////////////////////////////////
# End platform-specific directories.
#//////////////////////////////////////////////////////////////////////////////////////////
Modified: trunk/LayoutTests/platform/mac-wk2/TestExpectations (290120 => 290121)
--- trunk/LayoutTests/platform/mac-wk2/TestExpectations 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/LayoutTests/platform/mac-wk2/TestExpectations 2022-02-18 10:47:13 UTC (rev 290121)
@@ -126,6 +126,8 @@
webkit.org/b/235072 imported/w3c/web-platform-tests/video-rvfc/request-video-frame-callback-repeating.html [ Skip ]
+webanimations/frame-rate [ Pass ]
+
#//////////////////////////////////////////////////////////////////////////////////////////
# End platform-specific directories.
#//////////////////////////////////////////////////////////////////////////////////////////
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates-expected.txt (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates-expected.txt (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates-expected.txt 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,3 @@
+
+PASS Animations that start on consecutive frames with different, but compatible frame rates, eventually resolve in the same frame
+
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,54 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Animation.frameRate</title>
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const timestamps = new Map;
+
+ const timing = { duration: 10_000, iterations: Infinity };
+
+ await new Promise(resolve => {
+ const animations = [];
+
+ const addTimestamp = frameRate => {
+ const timestamp = document.timeline.currentTime;
+ if (!timestamps.has(timestamp))
+ timestamps.set(timestamp, new Set);
+ timestamps.get(timestamp).add(frameRate);
+
+ if (timestamps.get(timestamp).size == 3)
+ resolve();
+ };
+
+ const addAnimation = frameRate => {
+ const animation = document.timeline.animate(progress => addTimestamp(frameRate), timing);
+ animation.frameRate = frameRate;
+ animations.push(animation);
+ t.add_cleanup(() => animation.cancel());
+ };
+
+ // Start an animation that updates at 12fps.
+ addAnimation(12);
+
+ // Wait one frame before we start another animation running at 6fps.
+ // This new animation should update in sync with the 12fps animation.
+ requestAnimationFrame(() => {
+ addAnimation(6);
+
+ // Wait another frame before we start another animation running at 6fps.
+ // This new animation should update in sync with the 12fps animation.
+ requestAnimationFrame(() => {
+ addAnimation(18);
+ })
+ });
+ });
+}, "Animations that start on consecutive frames with different, but compatible frame rates, eventually resolve in the same frame");
+
+</script>
+</body>
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together-expected.txt (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together-expected.txt (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together-expected.txt 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,3 @@
+
+PASS Animations with the same frameRate started at once fire at the same timeline time
+
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together.html (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together.html (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-together.html 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Animation.frameRate</title>
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const timestamps = [];
+ const numberOfTimestamps = 4;
+
+ const frameRate = 30;
+ const timing = { duration: 10_000, iterations: Infinity };
+
+ const label1 = "one";
+ const label2 = "two";
+
+ await new Promise(resolve => {
+ let numberOfCallbacks = 0;
+
+ const animations = [];
+
+ const addTimestamp = label => {
+ timestamps.push({ label, value: document.timeline.currentTime });
+ if (timestamps.length == numberOfTimestamps)
+ resolve();
+ };
+
+ const addAnimation = label => {
+ const animation = document.timeline.animate(progress => addTimestamp(label), timing);
+ animation.frameRate = frameRate;
+ animations.push(animation);
+ t.add_cleanup(() => animation.cancel());
+ };
+
+ // Add two animations at once.
+ addAnimation(label1);
+ addAnimation(label2);
+ });
+
+ assert_equals(timestamps.length, numberOfTimestamps, "The expected number of timestamps were recorded");
+
+ assert_equals(timestamps[0].label, label1, `The first timestamp was for animation "${label1}"`);
+ assert_equals(timestamps[1].label, label2, `The second timestamp was for animation "${label2}"`);
+ assert_equals(timestamps[2].label, label1, `The third timestamp was for animation "${label1}"`);
+ assert_equals(timestamps[3].label, label2, `The fourth timestamp was for animation "${label2}"`);
+
+ assert_equals(timestamps[0].value, timestamps[1].value, "The first two timestamps match");
+ assert_equals(timestamps[2].value, timestamps[3].value, "The last two timestamps match");
+ assert_not_equals(timestamps[0].value, timestamps[3].value, "The first and last timestamps don't match");
+}, "Animations with the same frameRate started at once fire at the same timeline time");
+
+</script>
+</body>
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation-expected.txt (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation-expected.txt (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation-expected.txt 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,3 @@
+
+PASS Animations with the same frameRate started separately fire at the same timeline time
+
Added: trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html (0 => 290121)
--- trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html (rev 0)
+++ trunk/LayoutTests/webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html 2022-02-18 10:47:13 UTC (rev 290121)
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Animation.frameRate</title>
+<script src=""
+<script src=""
+<script src=""
+<body>
+<script>
+'use strict';
+
+promise_test(async t => {
+ const timestamps = [];
+ const numberOfTimestamps = 4;
+
+ const frameRate = 30;
+ const timing = { duration: 10_000, iterations: Infinity };
+
+ const label1 = "one";
+ const label2 = "two";
+
+ await new Promise(resolve => {
+ let numberOfCallbacks = 0;
+
+ const animations = [];
+
+ const addTimestamp = label => {
+ timestamps.push({ label, value: document.timeline.currentTime });
+ if (timestamps.length == numberOfTimestamps)
+ resolve();
+ };
+
+ const addAnimation = label => {
+ const animation = document.timeline.animate(progress => addTimestamp(label), timing);
+ animation.frameRate = frameRate;
+ animations.push(animation);
+ t.add_cleanup(() => animation.cancel());
+ };
+
+ // Add one animation now.
+ addAnimation(label1);
+
+ // And another one in a different frame
+ requestAnimationFrame(() => addAnimation(label2));
+ });
+
+ assert_equals(timestamps.length, numberOfTimestamps, "The expected number of timestamps were recorded");
+
+ assert_equals(timestamps[0].label, label1, `The first timestamp was for animation "${label1}"`);
+ assert_equals(timestamps[1].label, label2, `The second timestamp was for animation "${label2}"`);
+ assert_equals(timestamps[2].label, label1, `The third timestamp was for animation "${label1}"`);
+ assert_equals(timestamps[3].label, label2, `The fourth timestamp was for animation "${label2}"`);
+
+ assert_not_equals(timestamps[0].value, timestamps[1].value, "The first two do not match");
+ assert_equals(timestamps[2].value, timestamps[3].value, "The last two timestamps match");
+ assert_not_equals(timestamps[0].value, timestamps[3].value, "The first and last timestamps don't match");
+}, "Animations with the same frameRate started separately fire at the same timeline time");
+
+</script>
+</body>
Modified: trunk/Source/WebCore/ChangeLog (290120 => 290121)
--- trunk/Source/WebCore/ChangeLog 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/Source/WebCore/ChangeLog 2022-02-18 10:47:13 UTC (rev 290121)
@@ -1,3 +1,52 @@
+2022-02-18 Antoine Quint <[email protected]>
+
+ [frame-rate] animation with different but compatible frame rates should be aligned
+ https://bugs.webkit.org/show_bug.cgi?id=236778
+ <rdar://problem/89083932>
+
+ Reviewed by Simon Fraser.
+
+ With bug 234202 we started accounting for the frameRate property of an animation when updating
+ animations and ensuring that animations with the same frameRate are sampled in the same frame.
+
+ We expand on this by aligning animations that use different frameRate but could share sample frames.
+ For instance, if we have animations running with frameRate set to 6, 12 and 18, and assuming no
+ frames are ever skipped, we should expect a sequence of frames where the following animations are
+ sampled:
+
+ - frame 1: 12, 18, 6
+ - frame 2: 18
+ - frame 3: 12
+ - frame 4: 18
+ - frame 5: 12, 18, 6
+
+ and for that pattern to repeat.
+
+ To achieve this, we add a new "isNew" boolean flag on the FrameRateData struct. Then, in
+ FrameRateAligner::finishUpdate(), which is newly called from updateAnimationsAndSendEvents(),
+ we identify which frame rates have this flag, and analyze the other frame rates to determine
+ which frame rate it is most compatible with. If we find such a compatible frame rate, the new
+ frame rate adopts the same firstUpdateTime which ensures that they now have the same basis to
+ compute ideal times for sampling.
+
+ Within updateAnimationsAndSendEvents(), we also ensure that newly-added animations, regardless
+ of the output of FrameRateAligner::updateFrameRate(), are sampled right away so that there's no
+ delay for an animation to have visual output, letting them be synchronized with other animations
+ of that frame rate at the next opportunity.
+
+ Tests: webanimations/frame-rate/animation-frame-rate-alignment-compatible-frame-rates.html
+ webanimations/frame-rate/animation-frame-rate-alignment-started-together.html
+ webanimations/frame-rate/animation-frame-rate-alignment-started-with-one-frame-separation.html
+
+ * animation/DocumentTimelinesController.cpp:
+ (WebCore::DocumentTimelinesController::updateAnimationsAndSendEvents):
+ * animation/FrameRateAligner.cpp:
+ (WebCore::FrameRateAligner::beginUpdate):
+ (WebCore::FrameRateAligner::updateFrameRate):
+ (WebCore::frameRatesCanBeAligned):
+ (WebCore::FrameRateAligner::finishUpdate):
+ * animation/FrameRateAligner.h:
+
2022-02-18 Zan Dobersek <[email protected]>
[GTK][WPE] Don't use ANGLE's GBM-based display
Modified: trunk/Source/WebCore/animation/DocumentTimelinesController.cpp (290120 => 290121)
--- trunk/Source/WebCore/animation/DocumentTimelinesController.cpp 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/Source/WebCore/animation/DocumentTimelinesController.cpp 2022-02-18 10:47:13 UTC (rev 290121)
@@ -137,7 +137,10 @@
if (animationFrameRate) {
ASSERT(*animationFrameRate > 0);
- if (m_frameRateAligner.updateFrameRate(*animationFrameRate) == FrameRateAligner::ShouldUpdate::No)
+ auto shouldUpdate = m_frameRateAligner.updateFrameRate(*animationFrameRate);
+ // Even if we're told not to update, any newly-added animation should fire right away,
+ // it will align with other animations of that frame rate at the next opportunity.
+ if (shouldUpdate == FrameRateAligner::ShouldUpdate::No && !animation->pending())
continue;
}
@@ -156,6 +159,8 @@
}
}
+ m_frameRateAligner.finishUpdate();
+
// If the maximum frame rate we've encountered is the same as the default frame rate,
// let's reset it to not have an explicit value which will indicate that there is no
// need to override the default animation frame rate to service animations.
Modified: trunk/Source/WebCore/animation/FrameRateAligner.cpp (290120 => 290121)
--- trunk/Source/WebCore/animation/FrameRateAligner.cpp 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/Source/WebCore/animation/FrameRateAligner.cpp 2022-02-18 10:47:13 UTC (rev 290121)
@@ -48,6 +48,10 @@
const auto nextUpdateTimeEpsilon = 1_ms;
for (auto& [frameRate, data] : m_frameRates) {
+ // We can reset isNew to false for all entries since they were already present
+ // in the previous update.
+ data.isNew = false;
+
// If the timeline frame rate is the same as this animation frame rate, then
// we don't need to compute the next ideal sample time.
if (timelineFrameRate == frameRate) {
@@ -78,13 +82,48 @@
}
// We're dealing with a frame rate we didn't see in the previous update. In this case,
- // we'll allow animations to be sampled right away.
- // FIXME: We need make sure to reset the last update time to align this new frame rate
- // with other compatible frame rates.
+ // we'll allow animations to be sampled right away. Later, in finishUpdate(), we'll
+ // make sure to reset the last update time to align this new frame rate with other
+ // compatible frame rates.
m_frameRates.set(frameRate, FrameRateData { m_timestamp, m_timestamp });
return ShouldUpdate::Yes;
}
+// For two frame rates to be aligned, one must be the multitple of the other, or vice versa.
+static bool frameRatesCanBeAligned(FramesPerSecond a, FramesPerSecond b)
+{
+ return (a > b && a % b == 0) || (b > a && b % a == 0);
+}
+
+void FrameRateAligner::finishUpdate()
+{
+ // Iterate through the frame rates to find new entries and set their first update time
+ // in a way that future updates will be synchronized with other animations with that
+ // frame rate.
+ for (auto& [frameRate, data] : m_frameRates) {
+ if (!data.isNew)
+ continue;
+
+ // Look for the compatible frame rate with the highest value.
+ std::optional<FramesPerSecond> highestCompatibleFrameRate;
+ for (auto& [potentiallyCompatibleFrameRate, potentiallyCompatibleData] : m_frameRates) {
+ if (potentiallyCompatibleData.isNew)
+ continue;
+
+ if (frameRatesCanBeAligned(frameRate, potentiallyCompatibleFrameRate)) {
+ if (!highestCompatibleFrameRate || *highestCompatibleFrameRate > potentiallyCompatibleFrameRate)
+ highestCompatibleFrameRate = potentiallyCompatibleFrameRate;
+ }
+ }
+
+ // If we don't find any compatible frame rate, we can leave the last update time as-is
+ // and use the current timestamp as the basis from which we'll align animations for this
+ // frame rate.
+ if (highestCompatibleFrameRate)
+ data.firstUpdateTime = m_frameRates.get(*highestCompatibleFrameRate).firstUpdateTime;
+ }
+}
+
std::optional<Seconds> FrameRateAligner::timeUntilNextUpdateForFrameRate(FramesPerSecond frameRate, ReducedResolutionSeconds timestamp) const
{
auto it = m_frameRates.find(frameRate);
Modified: trunk/Source/WebCore/animation/FrameRateAligner.h (290120 => 290121)
--- trunk/Source/WebCore/animation/FrameRateAligner.h 2022-02-18 09:19:04 UTC (rev 290120)
+++ trunk/Source/WebCore/animation/FrameRateAligner.h 2022-02-18 10:47:13 UTC (rev 290121)
@@ -39,6 +39,7 @@
~FrameRateAligner();
void beginUpdate(ReducedResolutionSeconds, std::optional<FramesPerSecond>);
+ void finishUpdate();
enum class ShouldUpdate { Yes, No };
ShouldUpdate updateFrameRate(FramesPerSecond);
@@ -50,6 +51,7 @@
struct FrameRateData {
ReducedResolutionSeconds firstUpdateTime;
ReducedResolutionSeconds lastUpdateTime;
+ bool isNew { true };
};
HashMap<FramesPerSecond, FrameRateData> m_frameRates;